使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 28--开源电商商城系统项目实战--封装注册页面

测试学习记录,仅供参考!

项目实战(开源电商商城系统项目)

注册页面

登录页面场景完成之后,开始编写"注册页面"实现场景;

页面类
页面元素

1、在项目根目录 pageObject 软件包 register_page 目录下新建名称为 register_page.py 的 Python 文件;

把"注册页"的定位元素放到页面类中去管理;做 web 自动化主要的工作量就是定位元素(比较费时间);

复制代码
from selenium.webdriver.common.by import By
from util_tools.basePage import BasePage

# 注册页面类
class RegisterPage(BasePage):
    # url
    url = 'http://localhost:8088/ecshop/user.php?act=register'
    # 用户名
    username = (By.NAME, 'username')
    # email
    email = (By.NAME, 'email')
    # 密码
    password = (By.NAME, 'password')
    # 确认密码
    confirm_password = (By.ID, 'conform_password')
    # MSN
    msn = (By.NAME, 'extend_field1')
    # QQ
    qq = (By.NAME, 'extend_field2')
    # 办公电话
    office_phone = (By.NAME, 'extend_field3')
    # 家庭电话
    family_phone = (By.NAME, 'extend_field4')
    # 手机
    phone = (By.NAME, 'extend_field5')
    # 密码提示问题
    selects = (By.NAME, 'sel_question')
    # 密码问题答案
    password_answer = (By.NAME, 'passwd_answer')
    # 立即注册按钮
    submit_button = (By.NAME, 'Submit')
页面操作

2、页面类元素完成之后开始页面操作;

若是不怕搞混淆了的话,定位表达式的变量名和输入传参数据名一样亦可;

复制代码
from selenium.webdriver.common.by import By
from util_tools.basePage import BasePage

# 注册页面类
class RegisterPage(BasePage):
    # url
    url = 'http://localhost:8088/ecshop/user.php?act=register'
    # 用户名
    username = (By.NAME, 'username')
    # email
    email = (By.NAME, 'email')
    # 密码
    password = (By.NAME, 'password')
    # 确认密码
    confirm_password = (By.ID, 'conform_password')
    # MSN
    msn = (By.NAME, 'extend_field1')
    # QQ
    qq = (By.NAME, 'extend_field2')
    # 办公电话
    office_phone = (By.NAME, 'extend_field3')
    # 家庭电话
    family_phone = (By.NAME, 'extend_field4')
    # 手机
    phone = (By.NAME, 'extend_field5')
    # 密码提示问题
    selects = (By.NAME, 'sel_question')
    # 密码问题答案
    password_answer = (By.NAME, 'passwd_answer')
    # 立即注册按钮
    submit_button = (By.NAME, 'Submit')
    # 断言结果定位
    assert_result = (By.XPATH, '/html/body/div[7]/div[2]/div/div/div/font')
    # 断言用户已存在的结果
    assert_user_exist = (By.XPATH, '//*[@id="username_notice"]')

    # 注册操作--若不怕搞混淆,定位表达式的变量名和输入传参数据名一样也行
    def click_register(self, username, email, password, confirm_password, msn, qq, office_phone, family_phone, phone, password_answer):
        # 打开被测页面
        self.open_url(self.url)
        # 依次输入内容
        self.send_keys(self.username, username)
        self.send_keys(self.email, email)
        self.send_keys(self.password, password)
        self.send_keys(self.confirm_password, confirm_password)
        self.send_keys(self.msn, msn)
        self.send_keys(self.qq, qq)
        self.send_keys(self.office_phone, office_phone)
        self.send_keys(self.family_phone, family_phone)
        self.send_keys(self.phone, phone)
        self.send_keys(self.password_answer, password_answer)
        # 点击立即注册按钮
        self.click(self.submit_button)
封装方法

3、此时可以发现还剩下一个"下拉选择框"操作,在基类公共方法中亦没有封装此方法;

优化项目根目录 util_tools 软件包下 basePage.py 文件,再次封装 下拉菜单选择 方法;

导入下拉选择 from selenium.webdriver.support.ui import Select 类;

复制代码
# 导包
from pyxnat.core.uriutil import file_path
from selenium import webdriver
from selenium.common import NoSuchElementException
from selenium.webdriver.common.by import By
from time import sleep
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.wait import WebDriverWait
from config import setting
from util_tools.logs_util.recordlog import logs
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from datetime import datetime
import pytesseract
from PIL import Image
from selenium.webdriver.support.ui import Select


class BasePage(object):


    def __init__(self, driver):
        self.__driver = driver
        self.__wait = WebDriverWait(self.__driver, setting.WAIT_TIME)

    def window_max(self):
        self.__driver.maximize_window()

    def window_full(self):
        self.__driver.fullscreen_window()

    def screenshot(self):
        self.__driver.get_screenshot_as_png()

    def open_url(self, url):
        self.__driver.get(url)

    @property
    def current_url(self):
        return self.__driver.current_url

    @property
    def title(self):
        return self.__driver.title

    def refresh(self):
        self.__driver.refresh()

    @property
    def switch_to(self):
        return self.__driver.switch_to

    def iframe(self, frame):
        self.switch_to.frame(frame)

    def exit_iframe(self):
        self.switch_to.default_content()

    @property
    def alert(self):
        return self.__wait.until(ec.alert_is_present())

    def alert_confirm(self):
        self.alert.accept()

    def alert_cancel(self):
        self.alert.dismiss()

    def location_element(self, by, value):
        try:
            element = self.__wait.until(ec.presence_of_element_located((by, value)))
            logs.info(f"找到元素:{by}={value}")
            return element
        except Exception as e:
            logs.error(f"未找到元素:{by}={value}")
            raise e

    def location_elements(self, by, value):
        try:
            self.__wait.until(ec.presence_of_all_elements_located((by, value)))
            elements = self.__driver.find_elements(by, value)
            logs.info(f"找到元素列表:{by}={value}")
            return elements
        except Exception as e:
            logs.error(f"未找到元素列表:{by}={value}")
            raise e

    def click(self, locator: tuple, force=False):
        try:
            element = self.location_element(*locator)
            if not force:
                self.__driver.execute_script("arguments[0].click()", element)
            else:
                self.__driver.execute_script("arguments[0].click({force:true})", element)
            logs.info(f"元素被点击:{locator}")
        except NoSuchElementException as e:
            logs.error(f"元素无法定位:{e}")
            raise e

    def send_keys(self, locator: tuple, data):
        try:
            element = self.location_element(*locator)
            element.send_keys(data)
            logs.info(f"元素被输入内容:{locator},输入的内容为:{data}")
        except NoSuchElementException as e:
            logs.error(f"元素无法定位:{e}")
            raise e

    def selects(self, locator: tuple, index):
        try:
            select = Select(self.location_element(*locator))
            select.select_by_index(index)
            logs.info(f'选择第{index}个数据')
        except NoSuchElementException as e:
            logs.error(f'元素无法定位:{e}')
            raise e

    def enter(self):
        try:
            ActionChains(self.__driver).send_keys(Keys.ENTER).perform()
            logs.info("按下回车键")
        except NoSuchElementException as e:
            logs.error(f"元素无法定位:{e}")
            raise e

    def right_click(self, locator: tuple):
        try:
            element = self.location_element(*locator)
            ActionChains(driver).context_click(element).perform()
            logs.info("执行鼠标右键点击操作")
        except NoSuchElementException as e:
            logs.error(f"元素无法定位:{e}")
            raise e

    def double_click(self, locator: tuple):
        try:
            element = self.location_element(*locator)
            ActionChains(driver).double_click(element).perform()
            logs.info("执行鼠标双击操作")
        except NoSuchElementException as e:
            logs.error(f"元素无法定位:{e}")
            raise e

    def screenshots(self, image_name):
        import os
        current_time = datetime.now().strftime("%Y%m%d%H%M%S")
        file_name = f"{image_name}-{current_time}.png"
        file_path = os.path.join(setting.FILE_PATH.get('screenshot'), file_name)
        self.__driver.get_screenshot_as_file(file_path)

    def clear(self, locator: tuple):
        try:
            element = self.location_element(*locator)
            element.clear()
            logs.info("清空文本")
        except NoSuchElementException as e:
            logs.error(f"元素无法定位:{e}")
            raise e

    def ocr_captcha(self, locator: tuple):
        captcha_element = self.location_element(*locator)
        captcha_path = setting.FILE_PATH['screenshot'] + '/captcha.png'
        captcha_element.screenshot(captcha_path)
        captcha_image = Image.open(captcha_path)
        try:
            captcha_text = pytesseract.image_to_string(captcha_image)
            logs.info(f"识别到的验证码为:{captcha_text}")
            return captcha_text
        except pytesseract.pytesseract.TesseractNotFoundError:
            logs.error("找不到tesseract,这是因为pytesseract模块依赖于TesseractOCR引擎来进行图像识别!")

    def assert_is_element_present(self, locator: tuple):
        try:
            element = self.__driver.find_element(*locator)
            assert element.is_displayed(), '元素不存在'
        except NoSuchElementException as e:
            logs.error(f'元素未找到:{e}')
            raise AssertionError('元素不存在')

    def assert_title(self, expect_title):
        assert expect_title in self.title

4、回到注册页面,完善注册页面相关的操作

复制代码
from selenium.webdriver.common.by import By
from util_tools.basePage import BasePage

# 注册页面类
class RegisterPage(BasePage):
    # url
    url = 'http://localhost:8088/ecshop/user.php?act=register'
    # 用户名
    username = (By.NAME, 'username')
    # email
    email = (By.NAME, 'email')
    # 密码
    password = (By.NAME, 'password')
    # 确认密码
    confirm_password = (By.ID, 'conform_password')
    # MSN
    msn = (By.NAME, 'extend_field1')
    # QQ
    qq = (By.NAME, 'extend_field2')
    # 办公电话
    office_phone = (By.NAME, 'extend_field3')
    # 家庭电话
    family_phone = (By.NAME, 'extend_field4')
    # 手机
    phone = (By.NAME, 'extend_field5')
    # 密码提示问题
    select = (By.NAME, 'sel_question')
    # 密码问题答案
    password_answer = (By.NAME, 'passwd_answer')
    # 立即注册按钮
    submit_button = (By.NAME, 'Submit')
    # 断言结果定位
    assert_result = (By.XPATH, '/html/body/div[7]/div[2]/div/div/div/font')
    # 断言用户已存在的结果
    assert_user_exist = (By.XPATH, '//*[@id="username_notice"]')

    # 注册操作--若不怕搞混淆,定位表达式的变量名和输入传参数据名一样也行
    def click_register(self, username, email, password, confirm_password, msn, qq, office_phone, family_phone, phone, password_answer):
        # 打开被测页面
        self.open_url(self.url)
        # 依次输入内容
        self.send_keys(self.username, username)
        self.send_keys(self.email, email)
        self.send_keys(self.password, password)
        self.send_keys(self.confirm_password, confirm_password)
        self.send_keys(self.msn, msn)
        self.send_keys(self.qq, qq)
        self.send_keys(self.office_phone, office_phone)
        self.send_keys(self.family_phone, family_phone)
        self.send_keys(self.phone, phone)
        # 下拉菜单选择--若不固定索引值请在上面方法中自行加个参数
        self.selects(self.select, 3)
        self.send_keys(self.password_answer, password_answer)
        # 点击立即注册按钮
        self.click(self.submit_button)
测试类

5、 在项目根目录 testcase 软件包 register 目录下新建名称为 test_register.py 的 Python 文件;

用于管理注册页的测试用例;

1)、第一步,导入页面类;

2)、定义测试类;(可以参考登录)

3)、编写注册的测试用例;

复制代码
from pageObject.register_page.register_page import RegisterPage

class TestRegister:

    # 注册成功场景
    def test_register_success(self):
        pass

    # 注册失败场景
    def test_register_failed(self):
        pass
参数化

自行选择使用参数化处理数据文件驱动方式;使用 json 的话参数比较多,建议使用 Yaml、Excel 文件;

6、在 data 文件目录下新建一个名称为 register_success.yaml 的 Yaml 文件(注册成功场景)

注意符合格式要求

复制代码
- test01, 01@126.com, 123456, 123456, 22@qq.com, 123456, 123, 456, 789, 注册测试

7、完善注册成功场景的测试用例;

复制代码
# 导包
from pageObject.register_page.register_page import RegisterPage
import pytest
from time import sleep
from util_tools.handle_data.readYaml import read_yaml

class TestRegister:

    # 注册成功场景
    @pytest.mark.parametrize('data', read_yaml('./data/register_success.yaml'))
    def test_register_success(self, get_driver, data):
        # 调用页面类相关的操作--直接调用RegisterPage()把浏览器对象get_driver传给它
        register_page = RegisterPage(get_driver)
        # 页面类实例化之后开始进行解包数据
        username, email, password, confirm_password, msn, qq, office_phone, family_phone, phone, password_answer = data
        # 使用页面类对象 register_page 去调用相关操作 click_register
        register_page.click_register(username, email, password, confirm_password, msn, qq, office_phone, family_phone, phone, password_answer)
        # 添加等待时间查看效果
        sleep(3)

8、运行查看结果是能够成功的,但是有很多的参数,在一行显示就很长,若是有几十上百个参数,很多这种场景的话,都通过这样解包的方式就比较麻烦,需要优化;

直接使用 *data 去接收多个参数,但是有一个前提是:data 的元素数量(读取文件中以逗号分隔的每一组参数为一个)务必与方法(页面类中的方法)里面的入参的参数数量是一致的,才能使用" *data";

复制代码
# 导包
from pageObject.register_page.register_page import RegisterPage
import pytest
from time import sleep
from util_tools.handle_data.readYaml import read_yaml

class TestRegister:

    # 注册成功场景
    @pytest.mark.parametrize('data', read_yaml('./data/register_success.yaml'))
    def test_register_success(self, get_driver, data):
        # 调用页面类相关的操作--直接调用RegisterPage()把浏览器对象get_driver传给它
        register_page = RegisterPage(get_driver)
        # 如果使用 *data 解包数据,data的元素数量必须跟click_register方法的入参个数保持一致
        register_page.click_register(*data)
        # 添加等待时间查看效果
        sleep(3)
        # 断言结果
        register_page.assert_is_element_present(register_page.assert_result)

9、编写注册失败场景的测试用例代码; 在 data 文件目录下新建一个名称为 register_failed.yaml 的 Yaml 文件(注册失败场景),直接使用刚刚注册成功场景的数据,因为执行注册成功场景后,test01 用户已经存在了,此时再去测试"去注册已经存在的用户是否还能继续注册";

注册成功场景数据

复制代码
- test02, 02@126.com, 123456, 123456, 22@qq.com, 123456, 123, 456, 789, 注册测试

注册失败场景数据

复制代码
- test02, 02@126.com, 123456, 123456, 22@qq.com, 123456, 123, 456, 789, 注册测试

10、完善测试用例;

复制代码
# 导包
from pageObject.register_page.register_page import RegisterPage
import pytest
from time import sleep
from util_tools.handle_data.readYaml import read_yaml

class TestRegister:

    # 注册成功场景
    @pytest.mark.parametrize('data', read_yaml('./data/register_success.yaml'))
    def test_register_success(self, get_driver, data):
        # 调用页面类相关的操作--直接调用RegisterPage()把浏览器对象get_driver传给它
        register_page = RegisterPage(get_driver)
        # 如果使用 *data 解包数据,data的元素数量必须跟click_register方法的入参个数保持一致
        register_page.click_register(*data)
        # 添加等待时间查看效果
        sleep(3)
        # 断言结果
        register_page.assert_is_element_present(register_page.assert_result)

    # 注册失败场景
    @pytest.mark.parametrize('data', read_yaml('./data/register_failed.yaml'))
    def test_register_failed(self, get_driver, data):
        register_page = RegisterPage(get_driver)
        register_page.click_register(*data)
        sleep(3)
        register_page.assert_is_element_present(register_page.assert_user_exist)

11、运行主函数 run.py 文件,可以发现注册成功和注册失败场景均能通过测试;但是当测试数据有多组场景的情况下,断言结果是不同的,每一组数据都去写一种断言结果是比较麻烦的,每一个测试用例的断言结果并不是通用的;优化断言结果通用(想个办法把断言结果写成通用的)

封装方法

12、优化项目根目录 util_tools 软件包下 basePage.py 文件,在公共函数中去写一个 判断元素不存在 的方法;

复制代码
# 导包
from pyxnat.core.uriutil import file_path
from selenium import webdriver
from selenium.common import NoSuchElementException, TimeoutException
from selenium.webdriver.common.by import By
from time import sleep
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.wait import WebDriverWait
from config import setting
from util_tools.logs_util.recordlog import logs
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from datetime import datetime
import pytesseract
from PIL import Image
from selenium.webdriver.support.ui import Select


class BasePage(object):


    def __init__(self, driver):
        self.__driver = driver
        self.__wait = WebDriverWait(self.__driver, setting.WAIT_TIME)

    def window_max(self):
        self.__driver.maximize_window()

    def window_full(self):
        self.__driver.fullscreen_window()

    def screenshot(self):
        self.__driver.get_screenshot_as_png()

    def open_url(self, url):
        self.__driver.get(url)

    @property
    def current_url(self):
        return self.__driver.current_url

    @property
    def title(self):
        return self.__driver.title

    def refresh(self):
        self.__driver.refresh()

    @property
    def switch_to(self):
        return self.__driver.switch_to

    def iframe(self, frame):
        self.switch_to.frame(frame)

    def exit_iframe(self):
        self.switch_to.default_content()

    @property
    def alert(self):
        return self.__wait.until(ec.alert_is_present())

    def alert_confirm(self):
        self.alert.accept()

    def alert_cancel(self):
        self.alert.dismiss()

    def location_element(self, by, value):
        try:
            element = self.__wait.until(ec.presence_of_element_located((by, value)))
            logs.info(f"找到元素:{by}={value}")
            return element
        except Exception as e:
            logs.error(f"未找到元素:{by}={value}")
            raise e

    def location_elements(self, by, value):
        try:
            self.__wait.until(ec.presence_of_all_elements_located((by, value)))
            elements = self.__driver.find_elements(by, value)
            logs.info(f"找到元素列表:{by}={value}")
            return elements
        except Exception as e:
            logs.error(f"未找到元素列表:{by}={value}")
            raise e

    def click(self, locator: tuple, force=False):
        try:
            element = self.location_element(*locator)
            if not force:
                self.__driver.execute_script("arguments[0].click()", element)
            else:
                self.__driver.execute_script("arguments[0].click({force:true})", element)
            logs.info(f"元素被点击:{locator}")
        except NoSuchElementException as e:
            logs.error(f"元素无法定位:{e}")
            raise e

    def send_keys(self, locator: tuple, data):
        try:
            element = self.location_element(*locator)
            element.send_keys(data)
            logs.info(f"元素被输入内容:{locator},输入的内容为:{data}")
        except NoSuchElementException as e:
            logs.error(f"元素无法定位:{e}")
            raise e

    def selects(self, locator: tuple, index):
        try:
            select = Select(self.location_element(*locator))
            select.select_by_index(index)
            logs.info(f'选择第{index}个数据')
        except NoSuchElementException as e:
            logs.error(f'元素无法定位:{e}')
            raise e

    def enter(self):
        try:
            ActionChains(self.__driver).send_keys(Keys.ENTER).perform()
            logs.info("按下回车键")
        except NoSuchElementException as e:
            logs.error(f"元素无法定位:{e}")
            raise e

    def right_click(self, locator: tuple):
        try:
            element = self.location_element(*locator)
            ActionChains(driver).context_click(element).perform()
            logs.info("执行鼠标右键点击操作")
        except NoSuchElementException as e:
            logs.error(f"元素无法定位:{e}")
            raise e

    def double_click(self, locator: tuple):
        try:
            element = self.location_element(*locator)
            ActionChains(driver).double_click(element).perform()
            logs.info("执行鼠标双击操作")
        except NoSuchElementException as e:
            logs.error(f"元素无法定位:{e}")
            raise e

    def screenshots(self, image_name):
        import os
        current_time = datetime.now().strftime("%Y%m%d%H%M%S")
        file_name = f"{image_name}-{current_time}.png"
        file_path = os.path.join(setting.FILE_PATH.get('screenshot'), file_name)
        self.__driver.get_screenshot_as_file(file_path)

    def clear(self, locator: tuple):
        try:
            element = self.location_element(*locator)
            element.clear()
            logs.info("清空文本")
        except NoSuchElementException as e:
            logs.error(f"元素无法定位:{e}")
            raise e

    def ocr_captcha(self, locator: tuple):
        captcha_element = self.location_element(*locator)
        captcha_path = setting.FILE_PATH['screenshot'] + '/captcha.png'
        captcha_element.screenshot(captcha_path)
        captcha_image = Image.open(captcha_path)
        try:
            captcha_text = pytesseract.image_to_string(captcha_image)
            logs.info(f"识别到的验证码为:{captcha_text}")
            return captcha_text
        except pytesseract.pytesseract.TesseractNotFoundError:
            logs.error("找不到tesseract,这是因为pytesseract模块依赖于TesseractOCR引擎来进行图像识别!")

    def assert_is_element_present(self, locator: tuple):
        try:
            element = self.__driver.find_element(*locator)
            assert element.is_displayed(), '元素不存在'
        except NoSuchElementException as e:
            logs.error(f'元素未找到:{e}')
            raise AssertionError('元素不存在')

    def assert_element_not_visible(self, locator: tuple):
        try:
            self.__wait.until(ec.invisibility_of_element_located(locator))
        except TimeoutException:
            logs.error('元素可见')

    def assert_title(self, expect_title):
        assert expect_title in self.title
改造通用断言结果

13、改造断言结果

复制代码
# 导包
from pageObject.register_page.register_page import RegisterPage
import pytest
from time import sleep
from util_tools.handle_data.readYaml import read_yaml

class TestRegister:

    # 注册成功场景
    @pytest.mark.parametrize('data', read_yaml('./data/register_success.yaml'))
    def test_register_success(self, get_driver, data):
        # 调用页面类相关的操作--直接调用RegisterPage()把浏览器对象get_driver传给它
        register_page = RegisterPage(get_driver)
        # 如果使用 *data 解包数据,data的元素数量必须跟click_register方法的入参个数保持一致
        register_page.click_register(*data)
        # 添加等待时间查看效果
        sleep(3)
        # 断言结果
        register_page.assert_is_element_present(register_page.assert_result)

    # 注册失败场景
    @pytest.mark.parametrize('data', read_yaml('./data/register_failed.yaml'))
    def test_register_failed(self, get_driver, data):
        register_page = RegisterPage(get_driver)
        register_page.click_register(*data)
        sleep(3)
        register_page.assert_element_not_visible(register_page.assert_result)

14、更改测试数据;

注册成功场景数据;(自行设置)

用户名、email 都不存在,可以注册成功;

复制代码
- test03, 03@126.com, 123456, 123456, 22@qq.com, 123456, 123, 456, 789, 注册测试

注册失败场景数据;(自行设置)

用户名已存在;

email 已存在;

复制代码
- test01, 09@126.com, 123456, 123456, 22@qq.com, 123456, 123, 456, 789, 注册测试
- test09, 02@126.com, 123456, 123456, 22@qq.com, 123456, 123, 456, 789, 注册测试

15、运行主函数 run.py 文件,可以发现注册成功和注册失败 3 条测试用例均通过测试;

16、可以使用随机函数等(自行学习了解)

未完待续。。。

相关推荐
码界奇点3 小时前
Python从0到100一站式学习路线图与实战指南
开发语言·python·学习·青少年编程·贴图
测试人社区-小明4 小时前
智能弹性伸缩算法在测试环境中的实践与验证
人工智能·测试工具·算法·机器学习·金融·机器人·量子计算
Laravel技术社区4 小时前
pytesseract 中英文 识别图片文字
python
YJlio5 小时前
Active Directory 工具学习笔记(10.8):AdInsight——保存与导出(证据留存、共享与二次分析)
数据库·笔记·学习
生骨大头菜5 小时前
使用python实现相似图片搜索功能,并接入springcloud
开发语言·python·spring cloud·微服务
绝不收费—免费看不了了联系我5 小时前
Fastapi的单进程响应问题 和 解决方法
开发语言·后端·python·fastapi
xqqxqxxq6 小时前
背单词软件技术笔记(V2.0扩展版)
java·笔记·python
最晚的py6 小时前
Python抓取ZLibrary元数据
爬虫·python
咖啡续命又一天6 小时前
Trae CN IDE 中 Python 开发的具体流程和配置总结
开发语言·ide·python·ai编程
IT·小灰灰7 小时前
告别“翻墙“烦恼:DMXAPI让Gemini-3-pro-thinking调用快如闪电
网络·人工智能·python·深度学习·云计算