手把手教你写演唱会抢票脚本,我踩过的坑你别再跳了!

   

每周一篇,“挑战!一句话编写自动化测试脚本”。今天我们来编写da麦网购买演唱会门票的自动化脚本(脚本仅供测试使用,请勿非合规使用)

 

前提

  • DeepSeek网站

  • 提示词+提示文档

  • 搭建好的自动化测试项目+测试环境

步骤

1、梳理自动化流程

首先去被测试系统,人工梳理一下自动化流程

注意:梳理时,建议直接找到对应的唯一前端元素(如class="input-search"),可大大增强代码一次性成功概率

image-20250626上午93236386

2、写好提示词

  • 提示词需指定浏览器

  • 指定脚本名称

  • 将流程以阿拉伯数字1-10,按照点击元素的顺序编写

例如:

根据项目结构和项目示例代码,编写自动化测试代码。要求:使用edge浏览器,新建damai文件夹,并以damai.py,damai.json,test_damai.py等形式为脚本命名
1、打开URL=https://www.damai.cn/?spm=a2oeg.search_category.top.dhome.5bde4d15hqgU4d网址,
2、找到class="input-search"的元素,并输入文本“法老「生于未来」”;
3、点击class="btn-search"元素;
4、点击class="items__txt__title"元素下边的<a>标签;
5、向下滑动至class="buy-link"元素,并点击该元素;
6、断言:如果成功跳转至登录页面则成功,否则断言失败。

3、DeepSeek

提示词提示文档上传DeepSeek中

注意:“深度思考”建议打开,可以看到DeepSeek思考过程

image-20250626上午93427329

4、粘贴代码到项目

按照DeepSeek给出的代码路径,粘贴代码至项目对应位置即可

image-20250626上午94134841

image-20250626上午94103669

5、运行,完成

在 scripts/tieba/test_sign.py 中右击使用pytest运行即可

image-20250625下午25211854

image-20250625下午25353836

自动化测试的项目框架可以根据小编以前的文章来自己搭建,当然也提供了懒人版一键下载

懒人版包含了自动化项目源码(采用selenium+pytest+allure,PO模型,数据分离的2025最新框架),DeepSeek提示文档(生成的代码可直接粘贴至源码运行,非常关键!),提示词(用来生成自动化测试脚本)

676766

懒人版下载链接:https://www.ldxp.cn/item/g5ajbk

淘宝链接:商品详情页https://h5.m.taobao.com/awp/core/detail.htm?ft=t&id=944940407025

问题:

本次da麦网自动化遇到一个很奇怪的Bug,有了解的小伙伴一块讨论一下。

该脚本用于国内网络的时候会抛出错误 ReadTimeoutError,但国外就一点问题没有。为啥呢?

image-20250626上午94643367

完整报错信息

/01-Develop/01-code/code_shopping/venv/bin/Python /Applications/PyCharm.app/Contents/plugins/python/helpers/pycharm/_jb_pytest_runner.py --target test_damai.py::TestDamai.test_search_ticket 
Testing started at 2:54 PM ...
Launching pytest with arguments test_damai.py::TestDamai::test_search_ticket --no-header --no-summary -q in /01-Develop/01-code/code_shopping/scripts/damai
​
============================= test session starts ==============================
collecting ... collected 1 item
​
test_damai.py::TestDamai::test_search_ticket 
​
================== 1 failed, 2 warnings in 126.96s (0:02:06) ===================
FAILED
scripts/damai/test_damai.py:27 (TestDamai.test_search_ticket)
self = <urllib3.connectionpool.HTTPConnectionPool object at 0x102035c10>
conn = <urllib3.connection.HTTPConnection object at 0x102035f40>
method = 'POST'
url = '/session/9cc5d627107c71b233ec1906df2c98d3/element/f.EE709217E43B2ED10833B704593AAC97.d.47A11EB6D3EA0964E807B1F824F2492D.e.59/click'
body = '{}'
headers = HTTPHeaderDict({'Accept': 'application/json', 'Content-Type': 'application/json;charset=UTF-8', 'User-Agent': 'selenium/4.31.0 (python mac)', 'Connection': 'keep-alive'})
retries = Retry(total=3, connect=None, read=None, redirect=None, status=None)
timeout = Timeout(connect=120, read=120, total=None), chunked = False
response_conn = None, preload_content = True, decode_content = True
enforce_content_length = True
​
    def _make_request(
        self,
        conn: BaseHTTPConnection,
        method: str,
        url: str,
        body: _TYPE_BODY | None = None,
        headers: typing.Mapping[str, str] | None = None,
        retries: Retry | None = None,
        timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
        chunked: bool = False,
        response_conn: BaseHTTPConnection | None = None,
        preload_content: bool = True,
        decode_content: bool = True,
        enforce_content_length: bool = True,
    ) -> BaseHTTPResponse:
        """
        Perform a request on a given urllib connection object taken from our
        pool.
    
        :param conn:
            a connection from one of our connection pools
    
        :param method:
            HTTP request method (such as GET, POST, PUT, etc.)
    
        :param url:
            The URL to perform the request on.
    
        :param body:
            Data to send in the request body, either :class:`str`, :class:`bytes`,
            an iterable of :class:`str`/:class:`bytes`, or a file-like object.
    
        :param headers:
            Dictionary of custom headers to send, such as User-Agent,
            If-None-Match, etc. If None, pool headers are used. If provided,
            these headers completely replace any pool-specific headers.
    
        :param retries:
            Configure the number of retries to allow before raising a
            :class:`~urllib3.exceptions.MaxRetryError` exception.
    
            Pass ``None`` to retry until you receive a response. Pass a
            :class:`~urllib3.util.retry.Retry` object for fine-grained control
            over different types of retries.
            Pass an integer number to retry connection errors that many times,
            but no other types of errors. Pass zero to never retry.
    
            If ``False``, then retries are disabled and any exception is raised
            immediately. Also, instead of raising a MaxRetryError on redirects,
            the redirect response will be returned.
    
        :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int.
    
        :param timeout:
            If specified, overrides the default timeout for this one
            request. It may be a float (in seconds) or an instance of
            :class:`urllib3.util.Timeout`.
    
        :param chunked:
            If True, urllib3 will send the body using chunked transfer
            encoding. Otherwise, urllib3 will send the body using the standard
            content-length form. Defaults to False.
    
        :param response_conn:
            Set this to ``None`` if you will handle releasing the connection or
            set the connection to have the response release it.
    
        :param preload_content:
          If True, the response's body will be preloaded during construction.
    
        :param decode_content:
            If True, will attempt to decode the body based on the
            'content-encoding' header.
    
        :param enforce_content_length:
            Enforce content length checking. Body returned by server must match
            value of Content-Length header, if present. Otherwise, raise error.
        """
        self.num_requests += 1
    
        timeout_obj = self._get_timeout(timeout)
        timeout_obj.start_connect()
        conn.timeout = Timeout.resolve_default_timeout(timeout_obj.connect_timeout)
    
        try:
            # Trigger any extra validation we need to do.
            try:
                self._validate_conn(conn)
            except (SocketTimeout, BaseSSLError) as e:
                self._raise_timeout(err=e, url=url, timeout_value=conn.timeout)
                raise
    
        # _validate_conn() starts the connection to an HTTPS proxy
        # so we need to wrap errors with 'ProxyError' here too.
        except (
            OSError,
            NewConnectionError,
            TimeoutError,
            BaseSSLError,
            CertificateError,
            SSLError,
        ) as e:
            new_e: Exception = e
            if isinstance(e, (BaseSSLError, CertificateError)):
                new_e = SSLError(e)
            # If the connection didn't successfully connect to it's proxy
            # then there
            if isinstance(
                new_e, (OSError, NewConnectionError, TimeoutError, SSLError)
            ) and (conn and conn.proxy and not conn.has_connected_to_proxy):
                new_e = _wrap_proxy_error(new_e, conn.proxy.scheme)
            raise new_e
    
        # conn.request() calls http.client.*.request, not the method in
        # urllib3.request. It also calls makefile (recv) on the socket.
        try:
            conn.request(
                method,
                url,
                body=body,
                headers=headers,
                chunked=chunked,
                preload_content=preload_content,
                decode_content=decode_content,
                enforce_content_length=enforce_content_length,
            )
    
        # We are swallowing BrokenPipeError (errno.EPIPE) since the server is
        # legitimately able to close the connection after sending a valid response.
        # With this behaviour, the received response is still readable.
        except BrokenPipeError:
            pass
        except OSError as e:
            # MacOS/Linux
            # EPROTOTYPE and ECONNRESET are needed on macOS
            # https://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/
            # Condition changed later to emit ECONNRESET instead of only EPROTOTYPE.
            if e.errno != errno.EPROTOTYPE and e.errno != errno.ECONNRESET:
                raise
    
        # Reset the timeout for the recv() on the socket
        read_timeout = timeout_obj.read_timeout
    
        if not conn.is_closed:
            # In Python 3 socket.py will catch EAGAIN and return None when you
            # try and read into the file pointer created by http.client, which
            # instead raises a BadStatusLine exception. Instead of catching
            # the exception and assuming all BadStatusLine exceptions are read
            # timeouts, check for a zero timeout before making the request.
            if read_timeout == 0:
                raise ReadTimeoutError(
                    self, url, f"Read timed out. (read timeout={read_timeout})"
                )
            conn.timeout = read_timeout
    
        # Receive the response from the server
        try:
>           response = conn.getresponse()
​
../../venv/lib/python3.9/site-packages/urllib3/connectionpool.py:534: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../venv/lib/python3.9/site-packages/urllib3/connection.py:516: in getresponse
    httplib_response = super().getresponse()
/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/http/client.py:1349: in getresponse
    response.begin()
/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/http/client.py:316: in begin
    version, status, reason = self._read_status()
/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/http/client.py:277: in _read_status
    line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
​
self = <socket.SocketIO object at 0x1020568b0>, b = <memory at 0x101f93a00>
​
    def readinto(self, b):
        """Read up to len(b) bytes into the writable buffer *b* and return
        the number of bytes read.  If the socket is non-blocking and no bytes
        are available, None is returned.
    
        If *b* is non-empty, a 0 return value indicates that the connection
        was shutdown at the other end.
        """
        self._checkClosed()
        self._checkReadable()
        if self._timeout_occurred:
            raise OSError("cannot read from timed out object")
        while True:
            try:
>               return self._sock.recv_into(b)
E               socket.timeout: timed out
​
/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/socket.py:704: timeout
​
The above exception was the direct cause of the following exception:
​
self = <test_damai.TestDamai object at 0x101f988b0>
​
    @allure.story("搜索法老演唱会")
    @allure.title("大麦网搜索购票测试")
    @allure.description("测试搜索法老演唱会并验证登录跳转")
    def test_search_ticket(self):
        # 创建页面对象
        page_damai = PageDamai(self.driver)
    
        # 执行搜索流程
>       page_damai.page_search_flow(
            url=self.data_json[0],
            keyword=self.data_json[1]
        )
​
test_damai.py:36: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../page/damai/damai.py:64: in page_search_flow
    self.page_click_search_btn()
../../page/damai/damai.py:28: in page_click_search_btn
    self.common_click(damai.search_btn)
../../common/common.py:45: in common_click
    self.common_find_html_element(loc).click()
../../venv/lib/python3.9/site-packages/selenium/webdriver/remote/webelement.py:119: in click
    self._execute(Command.CLICK_ELEMENT)
../../venv/lib/python3.9/site-packages/selenium/webdriver/remote/webelement.py:572: in _execute
    return self._parent.execute(command, params)
../../venv/lib/python3.9/site-packages/selenium/webdriver/remote/webdriver.py:427: in execute
    response = self.command_executor.execute(driver_command, params)
../../venv/lib/python3.9/site-packages/selenium/webdriver/remote/remote_connection.py:404: in execute
    return self._request(command_info[0], url, body=data)
../../venv/lib/python3.9/site-packages/selenium/webdriver/remote/remote_connection.py:428: in _request
    response = self._conn.request(method, url, body=body, headers=headers, timeout=self._client_config.timeout)
../../venv/lib/python3.9/site-packages/urllib3/_request_methods.py:143: in request
    return self.request_encode_body(
../../venv/lib/python3.9/site-packages/urllib3/_request_methods.py:278: in request_encode_body
    return self.urlopen(method, url, **extra_kw)
../../venv/lib/python3.9/site-packages/urllib3/poolmanager.py:443: in urlopen
    response = conn.urlopen(method, u.request_uri, **kw)
../../venv/lib/python3.9/site-packages/urllib3/connectionpool.py:841: in urlopen
    retries = retries.increment(
../../venv/lib/python3.9/site-packages/urllib3/util/retry.py:474: in increment
    raise reraise(type(error), error, _stacktrace)
../../venv/lib/python3.9/site-packages/urllib3/util/util.py:39: in reraise
    raise value
../../venv/lib/python3.9/site-packages/urllib3/connectionpool.py:787: in urlopen
    response = self._make_request(
../../venv/lib/python3.9/site-packages/urllib3/connectionpool.py:536: in _make_request
    self._raise_timeout(err=e, url=url, timeout_value=read_timeout)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
​
self = <urllib3.connectionpool.HTTPConnectionPool object at 0x102035c10>
err = timeout('timed out')
url = '/session/9cc5d627107c71b233ec1906df2c98d3/element/f.EE709217E43B2ED10833B704593AAC97.d.47A11EB6D3EA0964E807B1F824F2492D.e.59/click'
timeout_value = 120
​
    def _raise_timeout(
        self,
        err: BaseSSLError | OSError | SocketTimeout,
        url: str,
        timeout_value: _TYPE_TIMEOUT | None,
    ) -> None:
        """Is the error actually a timeout? Will raise a ReadTimeout or pass"""
    
        if isinstance(err, SocketTimeout):
>           raise ReadTimeoutError(
                self, url, f"Read timed out. (read timeout={timeout_value})"
            ) from err
E           urllib3.exceptions.ReadTimeoutError: HTTPConnectionPool(host='localhost', port=50828): Read timed out. (read timeout=120)
​
../../venv/lib/python3.9/site-packages/urllib3/connectionpool.py:367: ReadTimeoutError
​
进程已结束,退出代码为 1

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值