解决Selenium的3大痛点!这款工具让你的自动化测试效率翻倍!

相信使用过Selenium WebDriver的小伙伴对其最大的诟病有3点,一是浏览器的driver和版本对应问题,第二是Selenium的执行速度,最后一个槽点是对页面元素文本值的断言非常不便。

在我们长期维护大量UI自动化测试用例的过程中这些痛点会让我们耗费不少精力和时间。

PlayWright 最佳实践

为何要选择 PlayWright?

对Selenium的痛点,PlayWright给出了完美的解决方案!

PlayWright的架构:

Playwright使用 Chrome DevTools 协议与 Chromium 通信。一旦触发测试,client端代码将被转换为JSON格式,然后使用websocket协议发送到服务器。


Playwright通过单个 websocket 协议连接传达所有请求,该连接将保持不变,直到所有测试执行完成。由于命令是在单个连接上发送的,因此测试失败或不稳定的可能性较小,并且命令可以快速执行。

Playwright启动速度比较快,拥有更多的定位方式,不需要安装驱动,能够在多个平台上运行,提供录制功能实现录制用例视屏,使用上来说Playwright也比较容易,无需过多封装即可直接使用.

环境部署

安装playwright前先配置好node和python环境,之后通过pip来安装playwright和其他库。

以Mac OS为例,执行如下命令:

  • 安装playwright库

pip install playwright
  • 然后安装browsers(会安装chromium,firefox,webkit浏览器)

playwright install
  • 如果只想安装指定的browser,则执行如下命令

playwright install chromium

重要的名词

在开始尝试使用playwright之前,需要先了解它的几个重要概念。

Browser:

是一个浏览器实例,代表一个浏览器会话。它是一个全局的上下文,可以包含多个 BrowserContext。

使用:创建浏览器实例管理浏览器生命周期:可以启动浏览器、关闭浏览器等。

 
  1. from playwright.sync_api import Playwright

  2. def test_api_show(playwright: Playwright):

  3. browser = playwright.chromium.launch()

BrowserContext:

是浏览器中的一个隔离环境,可以包含多个 Page。每个 BrowserContext有自己的浏览器存储,例如 cookies 和本地存储。

使用:创建新的上下文,管理上下文。可以添加页面、关闭页面等。

 
  1. from playwright.sync_api import Playwright

  2. def test_api_show(playwright: Playwright):

  3. browser = playwright.chromium.launch()

  4. context = browser.new_context()

Page:

是浏览器中的一个标签页,可以进行页面导航、操作 DOM 元素、捕获页面截图等。

使用:导航页面,操作 DOM 元素,捕获页面截图,处理页面事件。

 
  1. from playwright.sync_api import Playwright

  2. def test_api_show(playwright: Playwright):

  3. browser = playwright.chromium.launch()

  4. context = browser.new_context()

  5. page = context.new_page()

  6. page.goto("")

元素定位

元素定位是PlayWright的核心部分,我们会详细演示常用方法的使用。

get_by_placeholder:

根据页面元素的placeholder属性值定位

以上图为例,我们想定位<销售机会名称>输入框可以使用get_by_placeholder方法。

page.get_by_placeholder("销售机会名称").fill("商机名称")

get_by_role:

允许通过元素的ARIA角色、ARIA属性和可访问名称来定位它们。

初次使用get_by_role方法可能会有点懵,因为不知道元素的role该写为什么!

PlayWright官网上给出了role的类型:

role "alert" | "alertdialog" | "application" | "article" | "banner" | "blockquote" | "button" | "caption" | "cell" | "checkbox" | "code" | "columnheader" | "combobox" | "complementary" | "contentinfo" | "definition" | "deletion" | "dialog" | "directory" | "document" | "emphasis" | "feed" | "figure" | "form" | "generic" | "grid" | "gridcell" | "group" | "heading" | "img" | "insertion" | "link" | "list" | "listbox" | "listitem" | "log" | "main" | "marquee" | "math" | "meter" | "menu" | "menubar" | "menuitem" | "menuitemcheckbox" | "menuitemradio" | "navigation" | "none" | "note" | "option" | "paragraph" | "presentation" | "progressbar" | "radio" | "radiogroup" | "region" | "row" | "rowgroup" | "rowheader" | "scrollbar" | "search" | "searchbox" | "separator" | "slider" | "spinbutton" | "status" | "strong" | "subscript" | "superscript" | "switch" | "tab" | "table" | "tablist" | "tabpanel" | "term" | "textbox" | "time" | "timer" | "toolbar" | "tooltip" | "tree" | "treegrid" | "treeitem"

在上图销售机会新建页右上角,有<取消>和<保存>两个button组件。定位它们,可以用get_by_role。

page.get_by_role("button", name="保存").click()

上图中,销售阶段的下拉框选项值是一个div元素,除了xpath或者css-selector方式外很难想到其他发方式来定位到它。但是在PlayWright中,我们可以使用get_by_role来处理它,具体的代码如下:

 
  1. //点击下拉框选项值的父级元素,即:div。

  2. page.locator(parent_locator_path).locator("visible=true").click()

  3. // 直接处理选项值,根据has_text=””来选中下拉选项值

  4. page.get_by_role("listitem").filter(has_text=item_name).click()

get_by_text:

根据包含的文本值定位元素

page.locator('//div[@x-placement="bottom-start"]').get_by_text(model_name, exact=True).locator("visible=true").click()

locator:

可以在定位器的子树中找到一个与指定选择器匹配的元素。它还可以过滤选项值。

locator可以接受任何形式定位方式,也就是说locator_path可以是id,xpath,css,role等。

 
  1. locator(locator_path).locator("visible=true").click()

  2. page.locator("locator_path", has_text="")

  3. page.locator("locator_path", has_not_text="")

索引nth

大多数时候,我们会遇到在一个页面上存在多个类似元素的情况。

如下图,页面中存在多个类似的元素,这种业务场景下,我们可以考虑先定位一组元素然后根据返回的元素列表的索引来定位到具体的组件。

 
  1. relation_list =page.locator('//div[@class="field-input-block__suffix"]').locator("visible=true")

  2. relation_list.nth(element_index).click()

filter: 

这个方法根据选项缩小现有定位器的范围,例如按文本过滤。它可以被链接起来进行多次过滤。

page.get_by_role("listitem").filter(has_text=item_name).click()

交互动作

在UI自动化测试工作中,我们经常用到的页面交互无非是点击,输入,刷新这三个动作。

PlayWright对上面三种交互动作都提供了支持。

  • click:点击元素

page.get_by_placeholder(place_holder, exact=True).click()
  • fill:输入文本

page.locator(locator_path).locator("visible=true").fill(input_context)
  • reload():重载页面

page.reload()
  • select_option:从下拉选项值中选择

官网示例的页面元素

 
  1. <select multiple>

  2. <option value="red">Red</div>

  3. <option value="green">Green</div>

  4. <option value="blue">Blue</div>

  5. </select>

  • 使用select_option的示例:

 
  1. element.select_option("blue")

  2. element.select_option(label="blue")

  3. element.select_option(value=["red", "green", "blue"])

  • set_checked:设置复选框或单选按钮元素的状态

page.get_by_role("checkbox").set_checked(True)

强大的断言

PlayWright自带断言功能,并且我们可以很方便地获取到页面里各种容器元素下所有的文本值。

all_inner_texts/inner_text

 
  1. # 获取容器内的文本值

  2. def get_inner_text_on_list(self, data_key_name):

  3. # 根据列表页的主键获取一行数据的文本值(返回的是str,也是奇葩)

  4. text_list = self.page.get_by_role("row", name=data_key_name).locator("visible=true").inner_text()

  5. return text_list

断言expect

 
  1. # 判断系统里一闪而过的tips文案

  2. def assert_action_success(self):

  3. expect(self.page.locator('//p[@class="el-message__content"]')).to_contain_text("成功")

PageObject 模式封装

PO模式不作过多介绍了,相信读者朋友们或多或少都有了解。下面直接上代码。

封装playwright的基础操作

Base包里封装了页面元素的基本操作,包含了元素定位,点击输入,断言等通用功能点的封装。

 
  1. # -*- encoding = utf-8 -*-

  2. # Author:晴空-姜林斌

  3. # Date:2024-06-06

  4. from playwright.sync_api import sync_playwright, expect

  5. web_host = "https://appwebfront.xbongbong.com/"

  6. class Base:

  7. def __init__(self):

  8. self.playwright = sync_playwright().start()

  9. self.browser = self.playwright.chromium.launch(headless=False)

  10. self.context = self.browser.new_context()

  11. self.page = self.context.new_page()

  12. # 地址路由

  13. self.url_dict = {"MULTI_PROD_WEB_MARKET_LIST": "/#/market-manage/market-activity?subBusinessType=8100&appId=185800&menuId=2000341&saasMark=1&distributorMark=0",

  14. "MULTI_PROD_WEB_CLUE_LIST": "/#/market-manage/sales-leads?subBusinessType=8001&appId=185800&menuId=2000343&saasMark=1&distributorMark=0"}

 
 
  1. # 根据placeholder定位字段并输入内容

  2. def input_by_placeholder(self, place_holder, send_keys):

  3. self.page.get_by_placeholder(place_holder, exact=True).fill(send_keys)

 
  1. # 根据placeholder定位并点击

  2. def click_by_placeholder(self, place_holder):

  3. self.page.get_by_placeholder(place_holder, exact=True).click()

 
  1. # 输入编号信息(系统中的编号-流水号字段)

  2. def input_serial(self, send_keys):

  3. self.page.get_by_placeholder("流水号", exact=True).clear()

  4. self.page.get_by_placeholder("流水号", exact=True).fill(send_keys)

 
  1. # locator操作控件并输入内容 支持css-selector xpath id格式

  2. def input_by_locator(self, locator_path, input_context):

  3. self.page.locator(locator_path).locator("visible=true").fill(input_context)

 
  1. # 根据locator定位组件并点击

  2. def click_by_locator(self, locator_path):

  3. self.page.locator(locator_path).locator("visible=true").click()

  4. self.sleep(500)

 
  1. # 点击button组件

  2. def click_button(self, button_name):

  3. self.page.get_by_role("button", name=button_name, exact=True).click()

  4. if str(button_name) in "保存修改删除":

  5. self.sleep(2000)

  6. else:

  7. self.sleep(1000)

 
  1. # 根据下列选项的值选择

  2. def set_opportunity_stage(self, item_name):

  3. self.click_by_locator("//form/div[7]/div/div/div/div[1]/span")

  4. self.page.get_by_role("listitem").filter(has_text=item_name).click()

 
  1. # 设置任务的所属阶段

  2. def set_task_stage(self, item_name):

  3. self.click_by_locator("//form/div[8]/div/div/div/div/input")

  4. self.page.get_by_role("listitem").filter(has_text=item_name).click()

 
  1. # 设置风险的所属阶段

  2. def set_risk_stage(self, item_name):

  3. # 设置任务的所属阶段

  4. self.click_by_locator("//form/div[8]/div/div/div/div[1]/input")

  5. self.page.get_by_role("listitem").filter(has_text=item_name).click()

 
  1. # 设置企微销售机会的阶段

  2. def set_wechat_opportunity_stage(self, item_name):

  3. self.click_by_locator("//form/div[7]/div/div/div/div[1]/span")

  4. self.page.get_by_role("listitem").filter(has_text=item_name).click()

 
  1. # 关闭详情页

  2. def close_detail(self):

  3. self.click_by_locator('//div[@class="title__close-btn"]')

 
  1. # 新建页切换模板

  2. def switch_model(self, model_name):

  3. self.click_by_placeholder("请选择模板")

  4. self.page.locator('//div[@x-placement="bottom-start"]').get_by_text(model_name, exact=True).locator("visible=true").click()

  5. self.sleep(1000)

 
  1. # 选择单选

  2. def select_single_member(self, add_index, member_name):

  3. radio_button_list = self.page.locator('//div[@class="user-radio-btn"]').locator("visible=true")

  4. radio_button_list.nth(add_index).click()

  5. self.page.locator('//div[@class="person-tabs"]').get_by_placeholder("请输入内容").fill(member_name)

  6. self.sleep(1000)

  7. self.page.locator('//div[@class="user-tab-content__user"]/div/label/span[1]').click()

  8. self.sure_on_dialog()

 
  1. # 服务云 独用的成员单选

  2. def single_team_for_service_cloud(self, add_index, member_name):

  3. radio_button_list = self.page.locator('//div[@class="user-radio-btn-block"]').locator("visible=true")

  4. radio_button_list.nth(add_index).click()

  5. self.page.locator('//div[@class="person-tabs"]').get_by_placeholder("请输入内容").fill(member_name)

  6. self.sleep(1000)

  7. self.page.locator('//div[@class="user-tab-content__user"]/div/label/span[1]').click()

  8. self.sure_on_dialog()

 
  1. # 点击超链接

  2. def partial_link(self, link_text):

  3. locator_path = 'a:has-text("' + str(link_text) + '")'

  4. self.page.locator(locator_path).locator("visible=true").click()

  5. self.sleep(1000)

 
  1. # 单据详情页 更多-关联新建

  2. def new_on_detail(self, business_name):

  3. link_business_locator = 'li:has-text("' + str(business_name) + '")'

  4. self.page.get_by_role("button", name="", exact=True).click()

  5. self.page.locator(link_business_locator).click()

 
  1. # 单据新建/编辑页 选择所有关联产品

  2. def select_all_products(self):

  3. self.page.locator("span:has-text('添加产品')").locator("visible=true").click()

  4. self.page.locator("//table/thead/tr/th[1]/div/label/span").locator("visible=true").click()

  5. self.click_button("确定")

 
  1. # 单据详情页查看 更多-tab栏

  2. def view_more_tab_on_detail(self, tab_name):

  3. self.page.locator('//div[@class="detail-tabs-more-button"]').locator("visible=true").click()

  4. self.page.wait_for_timeout(1000)

  5. tab_path = "li:has-text('" + str(tab_name) + "')"

  6. self.page.locator(tab_path).locator("visible=true").click()

 
  1. # 详情页的复制/编辑/删除

  2. def copy_and_edit_on_detail(self, action_name):

  3. button_list = self.page.locator('//div[@class="detail-permission-bts"]/div').locator('[type="button"]').all()

  4. for button_element in button_list:

  5. if button_element.inner_text().__contains__(action_name):

  6. button_element.click()

  7. self.sleep(1000)

  8. break

  9. else:

  10. continue

 
  1. # 列表搜索

  2. def search_on_list(self, search_condition):

  3. self.page.locator('//div[@class="search"]/div/input').fill(search_condition)

  4. self.page.locator('//div[@class="search"]/div/div').locator("visible=true").click()

 
  1. # 列表页新建

  2. def new_on_list(self):

  3. self.click_button("新建")

 
  1. # 详情页的复制-编辑-删除

  2. def common_action_on_detail(self, action_name):

  3. locator_name = " " + str(action_name)

  4. self.page.get_by_role("button", name=locator_name).locator("visible=true").click()

 
  1. # 跳转到指定列表

  2. def go(self, url_key):

  3. list_url = self.url_dict[url_key]

  4. self.page.goto(web_host + str(list_url))

  5. self.sleep(3000)

 
  1. # 强制等待,单位毫秒

  2. def sleep(self, wait_duration):

  3. self.page.wait_for_timeout(wait_duration)

 
  1. # 刷新页面

  2. def refresh(self):

  3. self.page.reload(timeout=10000)

 
  1. # 关联数据选择 支持系统和自定义

  2. def select_relation_data(self, model, element_index, relation_data_name):

  3. if str(model) == "saas":

  4. saas_relation_list = self.page.locator('//div[@class="field-input-block__suffix"]').locator("visible=true")

  5. saas_relation_list.nth(element_index).click()

  6. # 搜索关联数据

  7. self.page.get_by_placeholder("搜索", exact=True).locator("visible=true").fill(relation_data_name)

  8. self.sleep(1000)

  9. self.page.locator('//table[@class="el-table__body"]/tbody/tr/td[1]/div/label').locator(

  10. "visible=true").click()

  11. self.sure_on_dialog()

  12. elif str(model) == "paas":

  13. paas_relation_list = self.page.locator('//div[@class="relation-data-input__right"]').locator("visible=true")

  14. paas_relation_list.nth(element_index).click()

  15. # 搜索关联数据

  16. self.page.get_by_placeholder("请输入内容", exact=True).locator("visible=true").fill(relation_data_name)

  17. self.sleep(1000)

  18. self.page.locator('//table[@class="el-table__body"]/tbody/tr/td[1]/div/label').locator(

  19. "visible=true").click()

  20. self.sure_on_dialog()

 
  1. # 非nvwa业务模块的关联数据选择

  2. def set_not_nvwa_relation_data(self, element_index, relation_data_name):

  3. relation_list = self.page.locator('//div[@class="field-input-block__suffix"]').locator("visible=true")

  4. relation_list.nth(element_index).click()

  5. # 搜索关联数据

  6. self.page.get_by_placeholder("搜索", exact=True).locator("visible=true").fill(relation_data_name)

  7. self.sleep(1000)

  8. self.page.locator('//table[@class="el-table__body"]/tbody/tr/td[1]/div/span/label').locator(

  9. "visible=true").click()

  10. self.sure_on_dialog()

 
  1. # 在列表全选后进行批量删除/彻底删除

  2. def batch_del_on_list(self, batch_action_name):

  3. # 列表页全选

  4. self.page.locator('#select-all').locator("visible=true").click()

  5. self.click_button(batch_action_name)

  6. self.sure_on_dialog()

 
  1. # 在系统的对话框中点击确定

  2. def sure_on_dialog(self):

  3. self.click_button("确定")

  4. self.sleep(1000)

 
  1. # 切换详情页tab

  2. def switch_tab(self, tab_name):

  3. self.sleep(1000)

  4. tab_list = self.page.locator('//div[@role="tablist"]').locator('//div[@role="tab"]').locator(

  5. "visible=true").all()

  6. for tab in tab_list:

  7. if str(tab.inner_text()).__contains__(tab_name):

  8. tab.click()

  9. self.sleep(1000)

  10. break

  11. else:

  12. continue

 
  1. # 获取容器内的文本值

  2. def get_inner_text_on_list(self, data_key_name):

  3. # 根据列表页的主键获取一行数据的文本值(返回的是str,也是奇葩)

  4. text_list = self.page.get_by_role("row", name=data_key_name).locator("visible=true").inner_text()

  5. return text_list

 
  1. # 获取基本信息栏容器内的文本值

  2. def get_inner_text_on_basic(self):

  3. basic_text = self.page.locator("div.base-detail__bottom--left > form").locator("visible=true").inner_text()

  4. return basic_text

 
  1. # 获取单据详情页-负责团队tab信息

  2. def get_sales_team_info(self):

  3. team_text = self.page.locator('//div[@class="sale-team-detail__body"]').locator("visible=true").inner_text()

  4. return team_text

 
  1. # 获取tab栏下的内容 model分为base和more分别对应基本tab和更多tab

  2. def get_inner_text_of_tab(self, model, tab_name):

  3. if str(model) == "base":

  4. self.switch_tab(tab_name)

  5. elif str(model) == "more":

  6. self.view_more_tab_on_detail(tab_name)

  7. else:

  8. pass

  9. # 获取tab栏下的inner-text

  10. tab_context = self.page.locator('//div[@class="detail-tab-containt"]').locator("visible=true").inner_text()

  11. return tab_context

 
  1. # 获取编辑页的inner-text

  2. def get_inner_text_of_update(self):

  3. update_page_text = self.page.locator('//div[@class="edit-dialog__content"]').locator(

  4. "visible=true").inner_text()

  5. return update_page_text

 
  1. # 判断新建/编辑/删除成功

  2. def assert_action_success(self):

  3. action_tips_list = self.page.locator('//p[@class="el-message__content"]').locator("visible=true")

  4. expect(action_tips_list.nth(0)).to_contain_text("成功")

测试用例:

考虑到实际的业务场景,笔者在该项目里并没有封装具体的页面而是封装好了base通用操作操作后直接使用了,如果读者朋友们有需要可以自行封装页面的操作。

ding_prod_web是具体的UI自动化测试用例的目录,具体的结构如下图。

集成到 Jenkins

在Jenkins里配置自由风格的Job

构建步骤是执行sh

python3 -m pytest /Users/sunnysky/PythonProj/PlayWrightWeb/ding_prod_web --alluredir=allure-results

构建后的操作配置为Allure Report,以下是生成的Allure报告

展望未来

人工智能技术将对自动化测试产生深远的影响。人工智能技术通过大数据、AI和机器学习,机器将学会如何测试;这将对自动化测试进一步提升测试的有效性。

自动化测试的自愈技术、大数据预测、图象识别等技术,将使得测试更加智能化、精准化。

结合了LLM的AI框架开始小荷才露尖尖角:https://github.com/Skyvern-AI/skyvern。

作为测试人员,我们需要不断学习新技术,适应新变化,以确保我们的测试工作能够为软件质量提供坚实的保障。

希望我的分享能为大家带来一些启发和帮助。让我们共同期待自动化测试技术更加美好的未来!

 

 感谢每一个认真阅读我文章的人!!!

作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。

软件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

 

          视频文档获取方式:
这份文档和视频资料,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!以上均可以分享,点下方小卡片即可自行领取。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/58601.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

5G基础知识

什么是 FDD 频分双工&#xff08;Frequency Division Duplexing&#xff09;&#xff0c;理解起来很简单&#xff0c;就是把上行和下行业务隔离在两个频段&#xff0c;互不干扰。 而 TDD 时分双工&#xff08;Time-Division Duplexing&#xff09;&#xff0c;是指上行下业务完…

使用 Elastic Observability 监控 dbt 管道

作者&#xff1a;来自 Elastic Almudena Sanz Oliv•Tamara Dancheva 了解如何使用 Elastic 设置 dbt 监控系统&#xff0c;该系统可主动发出数据处理成本峰值、每张表的行数异常以及数据质量测试失败的警报。 在 Elastic 可观察性组织内的数据分析团队中&#xff0c;我们使用 …

网站攻击,XSS攻击的类型

XSS&#xff08;跨站脚本&#xff09;攻击是一种网络安全攻击方式&#xff0c;攻击者通过在网站页面中注入恶意脚本&#xff0c;使脚本在其他用户的浏览器中执行&#xff0c;从而窃取用户信息、篡改页面内容或操控用户账户。这类攻击通常利用网站对输入数据的过滤不严格&#x…

2023年信息安全工程师摸底测试卷

目录 1.密码算法 2.等级保护 3.密码学 4.安全评估 5.网络安全控制技术 6.恶意代码 7.身份认证 8.资产管理 9.密码分类 10.被动攻击 11.商用密码服务​编辑 12.超文本传输协议 13.数字水印技术 14.信息系统安全设计 15.重放攻击 16.信息资产保护 17.身份认证 …

linux驱动—在自己的总线目录下创建属性文件

在总线目录下创建属性文件以扩展其功能。 通过创建属性文件&#xff0c; 我们可以为总线添加额外的信息和控制选项&#xff0c; 以便与设备和驱动进行交互。 简单就是&#xff0c;属性文件&#xff0c;可以完成用户空间和内核空间的数据交互&#xff0c; 比如在应用层快速修改g…

R向量运算数组矩阵

向量的运算 向量的加减乘除可以直接进运行&#xff0c;不用循环 向量之间的运算&#xff1a;分别对应计算&#xff0c;不用循环 两个运算的向量可以不是长度相等&#xff0c;但是一定长度要成整数倍。 每种运算都可以返回逻辑值T或F 取整函数 保留小数位用round&#xff1a; …

[Python学习日记-54] Python 中的日志模块 —— logging

[Python学习日记-54] Python 中的日志模块 —— logging 简介 基础用法 日志写入到文件 自定义日志格式 日志同时输出到屏幕和写入到文件 简介 在程序的运行过程中会执行很多操作或者进行很多的交互&#xff0c;也有的时候可能你开发出来的网站会遭到黑客的攻击&#xff0…

C++关键字noexcept应用及案例

文章目录 使用场景&#xff1a;注意事项&#xff1a; noexcept在C中的应用和重要性&#xff1a;与标准库的交互与异常安全相关的编程模式与C标准的关系与性能的关系示例代码 综合案例扩展后的代码新增功能解释异常安全性能优化 在C中&#xff0c; noexcept是一个关键字&#x…

STM32F103HAL库实现低功耗(睡眠模式、停止模式和待机模式)

STM32F103HAL库实现低功耗&#xff08;睡眠模式、停止模式和待机模式&#xff09; 1. STM32电源结构2. 电源管理器2.1 上电复位和掉电复位2.2 可编辑电压监测器&#xff08;PVD&#xff09; 3. 低功耗模式介绍3.1 睡眠模式3.2 停止模式3.3 待机模式 4. 低功耗相关寄存器5. 低功…

Windows: 如何实现CLIPTokenizer.from_pretrained`本地加载`stable-diffusion-2-1-base`

参考&#xff1a;https://blog.csdn.net/qq_38423499/article/details/137158458 https://github.com/VinAIResearch/Anti-DreamBooth?tabreadme-ov-file 联网下载没有问题&#xff1a; import osos.environ["HF_ENDPOINT"] "https://hf-mirror.com" i…

【vue】14.插槽:构建可复用组件的关键

今天看代码的时候碰到了插槽&#xff0c;有些看不懂&#xff0c;所以写下这篇文章&#xff0c;系统地梳理一下关于插槽的内容&#xff0c;也希望给大家带来一些帮助。 // 我碰到的插槽长这样 <template #default"scope">... </template> 一.什么是插槽…

camera和lidar外参标定

雷达和相机的外参标定&#xff08;外部参数标定&#xff09;指的是确定两者之间的旋转和平移关系&#xff0c;使得它们的坐标系可以对齐。 文章目录 无目标标定livox_camera_calibdirect_visual_lidar_calibration 有目标标定velo2cam_calibration 无目标标定 livox_camera_ca…

数据结构和算法-动态规划(3)-经典问题

动态规划常见问题 打家劫舍 题目 [力扣198] 198. 打家劫舍 - 力扣&#xff08;LeetCode&#xff09; 题目描述 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&…

深入理解Redis的四种模式

Redis是一个内存数据存储系统&#xff0c;支持多种不同的部署模式。以下是Redis的四种主要部署模式。 1、单机模式 单机模式是最简单的部署模式&#xff0c;Redis将数据存储在单个节点上。这个节点包括一个Redis进程和一个持久化存储。单机模式非常适合小型应用程序或者开发和…

Flutter实战短视频课程

1、课程导学 一套代研运行多蜡 体州一致&#xff0c;目胜能优昇 未来大趋势 不改交原生项目的基础上&#xff0c;扩展Flutter能力 Flutter原生灵话切涣 0入侵 最简单、最通用 最新Flutter 3,x新特性讲解 大量flutter官方组件和api学习 最常用的第三方库使用及原理解析 自研组…

消息队列-Rabbitmq(消息发送,消息接收)

将来我们开发业务功能的时候&#xff0c;肯定不会在控制台收发消息&#xff0c;而是应该基于编程的方式。由于RabbitMQ采用了AMQP协议&#xff0c;因此它具备跨语言的特性。任何语言只要遵循AMQP协议收发消息&#xff0c;都可以与RabbitMQ交互。并且RabbitMQ官方也提供了各种不…

QT相机连接与拍照

先看效果 编辑:已添加虚拟键盘辅助输入,添加二维码识别,用的QZxing 初始化 auto mainLayout = new QHBoxLayout(this);m_viewfinder = new QCameraViewfinder(this);m_viewfinder->setStyleSheet("border-radius: 20px;background-color:rgb(43,48,70)");mainL…

ubuntu openmpi安装(超简单)

openmpi安装 apt update apt install openmpi-bin openmpi-common libopenmpi-dev安装到此完毕 测试一下&#xff0c;success !

【C++】string 类深度解析:探秘字符串操作的核心

快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。 目录 &#x1f4af;前言 &#x1f4af;为什么要学习 string 类 &#xff08;一&#xff09;简化操作 &#xff08;二&#xff09;确保安全 &#xff08;三…

【EndNote版】如何在Word中引用文献

1、在Word中&#xff0c;鼠标光标放在所需插入文献的位置 2、点击选项卡中的“EndNote X9”&#xff0c;直接在EndNote中选中对应的文献 3、选中文献&#xff0c;点击工具栏中的“引用” 4、最后就可在Word中看到所插入的文献