07 APP 自动化- appium+pytest+allure框架封装
文章目录
- 一、PO
- 二、代码简单实现
- 项目框架预览:
- base_page.py
- dir_config.py
- get_data.py
- logger.py
- start_session.py
- config.yaml
- key_code.yaml
- launch_page_loc.py
- login_page_loc.py
- launch_page.py
- login_page.py
- test_login.py
- pytest.ini
- run.py
一、PO
PO 分为四层 :base 层、pageobjects 层、testcases 层、testdata 层
- 第一层 base 层:抽取每个共同的属性及行为进行封装,定义到 basepage 类中
- 第二层 pageobjects 层: 每个页面定义为一个类,类=属性+页面操作方法
- 属性:页面元素的定位语句 (By.xx,"‘定位语句’)====> pagelocations
- 页面操作方法 pageobjects 层
- web 页面可以展示很多内容 每个页面定位一个页面类 创建 xxxpage.py
- app 页面展示内容少点 每个页面定位一个页面类 创建 xxxpage.py
- 第三层:testcases 层 项目覆盖那些流程 unittest/pytest
- 用例1 用例2 ,用例n… 每个用例创建一个.py文件?并不是
- 冒烟用例
- 基于功能模块进行分类管理用例
- 用例1:
- 1、操作步骤:里面的操作是属于哪些页面=调用这页面类里面的方法
实例化页面类.方法() - 2、断言 验证测试点是否与预期一致
- 1、操作步骤:里面的操作是属于哪些页面=调用这页面类里面的方法
- 用例1:
pytest 和 allure 环境安装参考:用例管理框架pytest之fixtrue,conftest.py,allure报告以及logo定制
二、代码简单实现
项目框架预览:
base_page.py
import os.path
import time
from appium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from AppFrame.common.logger import FrameLogger as Log
from AppFrame.common import dir_config as dir
import AppFrame.common.get_data as dataclass BasePage:"""BasePage:定义每个页面的相同属性及方法相同属性?获取浏览器驱动对象(数据)相同方法?元素定位、点击、输入...等等操作日志错误截图测试报告"""def __init__(self,driver: webdriver.Remote):self.driver = driver# 等待元素可见def wait_ele_visibility(self,page_name,loc,timeout=15,poll_fre=0.5):try:WebDriverWait(self.driver,timeout,poll_fre).until(EC.visibility_of_element_located(loc))# WebElement对象Log().get_logger().info(f"在[{page_name}]页面,找到元素:{loc}可见")except:Log().get_logger().error(f"在[{page_name}]页面,未找到元素:{loc}可见!!!")raise# 等待元素存在def wait_ele_presence(self,page_name,loc,timeout=15,poll_fre=0.5):try:WebDriverWait(self.driver,timeout,poll_fre).until(EC.presence_of_element_located(loc))Log().get_logger().info(f"在[{page_name}]页面,找到元素:{loc}存在")except:Log().get_logger().error(f"在[{page_name}]页面,未找到元素:{loc}存在!!!")raisedef locator(self,page_name,loc):"""元素定位"""# 参数loc,里面包含两个参数# loc = (By.LINK_TEXT,'登录')# *loc,是把外面括号去掉,变成2个参数print(page_name,loc)try:el = self.driver.find_element(*loc) # WebElement 对象Log().get_logger().info(f"在[{page_name}]页面,定位到元素:{loc}")except:Log().get_logger().info(f"在[{page_name}]页面,未定位到元素:{loc}!!!")# 失败截图self.save_screenshot(page_name)raisereturn eldef input(self,page_name,loc,value):"""输入"""try:self.wait_ele_visibility(page_name, loc)self.locator(page_name,loc).send_keys(value)Log().get_logger().info(f"在{page_name}页面,元素{loc}输入:{value}!")except:Log().get_logger().error(f"在{page_name}页面,元素{loc}输入失败!")# 失败截图self.save_screenshot(page_name)raisedef click(self,page_name,loc):"""点击"""try:self.wait_ele_visibility(page_name, loc)self.locator(page_name,loc).click()Log().get_logger().info(f"在{page_name}页面,元素{loc}点击成功!")except:Log().get_logger().error(f"在{page_name}页面,元素{loc}点击失败!")# 失败截图self.save_screenshot(page_name)raisedef move_element(self,page_name,loc):"""鼠标键盘操作"""try:self.wait_ele_visibility(page_name, loc)ActionChains(self.driver).move_to_element(self.locator(page_name,loc)).click().perform()Log().get_logger().info(f"在{page_name}页面,鼠标移动到元素:{loc}")except:Log().get_logger().error(f"在{page_name}页面,鼠标移动到元素{loc}失败!")# 失败截图self.save_screenshot(page_name)raisedef save_screenshot(self,img_name):file_name = os.path.join(dir.screenshots_dir, img_name+'.png')self.driver.save_screenshot(file_name)Log().get_logger().info(f"失败截图,截取当前网页,存储的路径:{file_name}")def sleep(self,s):time.sleep(s)def switch_new_win(self):"""切换到最新打开的窗口"""wins = self.driver.window_handlesprint(wins)# 切换最后打开的窗口self.driver.switch_to.window(wins[1])def switch_iframe(self,page_name,loc):"""切换到对应的 iframe"""try:self.wait_ele_visibility(page_name, loc)self.driver.switch_to.frame(self.locator(page_name,loc))Log().get_logger().info(f"在{page_name}页面,切换到 iframe 元素{loc}成功!")except:Log().get_logger().error(f"在{page_name}页面,切换到 iframe 元素{loc}失败!")# 失败截图self.save_screenshot(page_name)raise# appbasepage:# 滑屏操作 上下左右滑def swipe(self,direction,duration=1000):""":param direction:up down left right:param duration::return:"""# 获取整个app屏幕的大小size = self.driver.get_window_size()x = size["width"]y = size["height"]# 左滑if direction.lower() == "left":self.driver.swipe(start_x=x * 0.9, end_x=x * 0.2, start_y=y * 0.9, end_y=y * 0.9, duration=1000)# 右滑if direction.lower() == "right":self.driver.swipe(start_x=x * 0.2, end_x=x * 0.9, start_y=y * 0.9, end_y=y * 0.9, duration=1000)# 上拉if direction.lower() == "up":self.driver.swipe(start_x=x * 0.5, end_x=x * 0.5, start_y=y * 0.2, end_y=y * 0.9, duration=1000)# 下拉if direction.lower() == "down":self.driver.swipe(start_x=x * 0.5, end_x=x * 0.5, start_y=y * 0.9, end_y=y * 0.2, duration=1000)def press_keys(self,page_name,loc,keys:list):"""模拟键盘输入"""try:self.wait_ele_visibility(page_name, loc)self.locator(page_name,loc).click()real_value = ""for key in keys:key_code = 0meta_state = Noneflag = Nonefor k,v in key.items():if k == "key":real_value += vkey_code = data.get_yaml_data("key_code.yaml",v)if k == "flag":flag = vif k == "meta":meta_state = vself.driver.press_keycode(int(key_code), metastate=meta_state, flags=flag)Log().get_logger().info(f"在{page_name}页面,元素{loc}输入:{real_value}!")except:Log().get_logger().error(f"在{page_name}页面,元素{loc}输入失败!")# 失败截图self.save_screenshot(page_name)raise# app 自动化测试 appium
# app 自动化测试+web 自动测试整合 公共类 basepage web自动化 webbasepage app自动化 appbasepage
# 日志+错误截图+测试报告输出
dir_config.py
import os# 根路径
base_dir = os.path.dirname( os.path.dirname(os.path.abspath(__file__)))
# 用例路径
testcases_dir = os.path.join(base_dir, 'testcases')
# 数据路径
testdata_dir = os.path.join(base_dir, 'testdata')
# 测试报告路径
reports_dir = os.path.join(base_dir, 'outputs/reports')
# 日志路径
logs_dir = os.path.join(base_dir, 'outputs/logs')
# 失败截图
screenshots_dir = os.path.join(base_dir, 'outputs/screenshots')
# 配置路径
config_dir = os.path.join(base_dir, 'config')
get_data.py
import yaml
import os
from AppFrame.common import dir_config as Dir
import jsonpathdef get_yaml_data(file_name, key=None):file_path = os.path.join(Dir.config_dir, file_name)with open(file_path, encoding="utf-8") as yaml_file:data = yaml.load(yaml_file, Loader=yaml.FullLoader) # 字典类型if key is not None:data = jsonpath.jsonpath(data, f"$..{key}")return data[0]return data
logger.py
from AppFrame.common import dir_config as Dir
import logging
import os
import timeclass FrameLogger:def get_logger(self):# 创建日志器logger = logging.getLogger("logger")# 日志输出当前级别及以上级别的信息,默认日志输出最低级别是warningif not logger.handlers:logger.setLevel(logging.INFO)# 创建控制台处理器----》输出控制台SH = logging.StreamHandler()# 创建文件处理器----》输出文件log_path = os.path.join(Dir.logs_dir, f"log_{time.strftime('%Y%m%d%H%M%S', time.localtime())}.txt")FH = logging.FileHandler(log_path,mode="w",encoding="utf-8")# 日志包含哪些内容 时间 文件 日志级别 :事件描述/问题描述formatter = logging.Formatter(fmt="[%(asctime)s] [%(filename)s] %(levelname)s :%(message)s",datefmt='%Y/%m/%d %H:%M:%S')logger.addHandler(SH)logger.addHandler(FH)SH.setFormatter(formatter)FH.setFormatter(formatter)return logger
start_session.py
import AppFrame.common.get_data as data
from appium.options.android import UiAutomator2Options
from appium import webdriverdef start_appium_session(port=4723, **kwargs):""" 启动会话 """# 获取启动配置参数desired_caps = data.get_yaml_data("config.yaml")# 增加其他启动参数for k, v in kwargs.items():desired_caps[k] = v# 发送命令给 appium serverdriver = webdriver.Remote(f'http://127.0.0.1:{port}',options=UiAutomator2Options().load_capabilities(desired_caps))return driver
config.yaml
'platformName': 'Android' # 指定操作系统
'platformVersion': '12' # 指定操作系统版本
'automationName': 'Uiautomator2' # 默认框架
'deviceName': '127.0.0.1:62001' # 指定设备名称
'appPackage': 'com.tal.kaoyan' # 被操作的应用程序包名
'appActivity': 'com.tal.kaoyan.ui.activity.SplashActivity' # 启动页面
'noReset': 'true' # true--不重置 false--重置
'app': 'F:\Pycharm\AppAuto\Class\kaoyan_v4.5.3.apk' # apk文件所在路径
key_code.yaml
# """
# KeyEvent 常量官方文档:https://developer.android.com/reference/android/view/KeyEvent#constants
# 按键功能 KeyCode值 key值 说明
#
# 数字键0-9 7 到 16 0-9 对应数字 0 到 9
# 字母键a-z 29 到 54 a-z 对应字母 a 到 z(在 Android 中,大写字母的 KeyCode 与小写相同,但需通过 Meta 键(模拟 Shift) 触发)
# 空格 62 blank 输入空格
# 回车 66 enter 确认 / 换行
# 删除 67 delete 删除字符(退格键)
# @ 77 @ 特殊字符@
#
# """"blank": "62"
"enter": "66"
"delete": "67"
"@": "77"
"," : "55"
"." : "56"
"~" : "126"
"*" : "15"
"0": "7"
"1": "8"
"2": "9"
"3": "10"
"4": "11"
"5": "12"
"6": "13"
"7": "14"
"8": "15"
"9": "16"
"a": "29"
"b": "30"
"c": "31"
"d": "32"
"e": "33"
"f": "34"
"g": "35"
"h": "36"
"i": "37"
"j": "38"
"k": "39"
"l": "40"
"m": "41"
"n": "42"
"o": "43"
"p": "44"
"q": "45"
"r": "46"
"s": "47"
"t": "48"
"u": "49"
"v": "50"
"w": "51"
"x": "52"
"y": "53"
"z": "54"
launch_page_loc.py
from appium.webdriver.common.appiumby import AppiumBy
# 该页面所有的定位
user_protocol_loc = (AppiumBy.ID, 'com.tal.kaoyan:id/tip_commit')
confirm_permission_loc= (AppiumBy.ID, 'com.tal.kaoyan:id/tv_ok')
login_page_loc.py
from appium.webdriver.common.appiumby import AppiumBy
# 手机号验证码登录/注册页面
phone_number_loc = (AppiumBy.ANDROID_UIAUTOMATOR,'new UiSelector().resourceId("com.tal.kaoyan:id/kylogin_phone_input_phonelayout")')
protocol_phone_loc = (AppiumBy.ID, 'com.tal.kaoyan:id/loginTreatyCheckboxPhone')
get_code_loc = (AppiumBy.ID, "com.tal.kaoyan:id/kylogin_phone_input_codeget")
code_input_loc = (AppiumBy.ANDROID_UIAUTOMATOR,'new UiSelector().resourceId("com.tal.kaoyan:id/kylogin_phone_input_code")')
login_code_loc = (AppiumBy.ID, 'com.tal.kaoyan:id/loginCodeLoginBtn')
password_login_loc = (AppiumBy.ID, 'com.tal.kaoyan:id/loginRegistorcodeAndPassword')# 密码登录页面
account_loc = (AppiumBy.ID, 'com.tal.kaoyan:id/loginEmailEdittext')
password_loc= (AppiumBy.ID, 'com.tal.kaoyan:id/rtlLoginLayout')
protocol_password_loc = (AppiumBy.ID, 'com.tal.kaoyan:id/loginTreatyCheckboxPassword')
login_password_loc = (AppiumBy.ID, 'com.tal.kaoyan:id/loginLoginBtn')
launch_page.py
from AppFrame.base.base_page import BasePage
import AppFrame.pagelocators.launch_page_loc as locsclass LaunchPage(BasePage):# 启动各弹窗按钮操作def launch_close_prompt(self):# -------------首次启动后弹窗处理----------------try:# 等待用户协议弹窗元素出现(最多5秒)self.click("启动页面",locs.user_protocol_loc)except Exception:pass # 未出现弹窗,继续执行后续代码try:# 等待权限弹窗元素出现(最多5秒)self.click("启动页面",locs.confirm_permission_loc)except Exception:pass # 未出现弹窗,继续执行后续代码
login_page.py
from time import sleepfrom AppFrame.base.base_page import BasePage
import AppFrame.pagelocators.login_page_loc as locs
class LoginPage(BasePage):# 密码登录操作def password_login(self,account,password):# 点击密码登录按钮self.click("登录",locs.password_login_loc)# 输入账户self.input("login",locs.account_loc,account)# 输入密码self.press_keys("登录",locs.password_loc,password)# 同意协议self.click("登录",locs.protocol_password_loc)# 点击登录self.click("登录",locs.login_password_loc)# 手机号验证码登录操作def code_login(self,phone_number,password):# 输入手机号self.press_keys("登录", locs.phone_number_loc, phone_number)# 同意协议self.click("登录", locs.protocol_phone_loc)# 点击获取验证码self.click("登录", locs.get_code_loc)# 等待用户输入code_value = input("请输入手机验证码")self.input("登录",locs.code_input_loc,code_value)# 点击登录self.click("登录", locs.login_code_loc)sleep(2)
test_login.py
# -*- coding: utf-8 -*-
from AppFrame.pageobjects.launch_page import LaunchPage
from AppFrame.pageobjects.login_page import LoginPage
import AppFrame.common.start_session as start
import allure
import pytest@allure.epic("项目名称:考研帮-app自动化测试")
@allure.feature("模块名称:登录模块")
class TestLogin:@allure.story("用例名称:正确账户密码登录成功")@allure.severity(allure.severity_level.NORMAL)@pytest.mark.user@pytest.mark.smokedef test_login_success(self):driver = start.start_appium_session(noReset = 'false')launch_page = LaunchPage(driver)launch_page.launch_close_prompt()login_page = LoginPage(driver)account = "17719847692"password = [{"key":"h"},{"key":"c"},{"key":"@"},{"key":"1"},{"key":"2"},{"key":"3"},{"key":"4"},{"key":"5"},{"key":"6"}]login_page.password_login(account,password)
pytest.ini
[pytest]
addopts = -vs -m 'smoke' --alluredir=outputs/logs --clean-alluredir
testpaths = testcases/
python_files = test_*.py
python_classes = Test*
python_functions = test_*
markers = smoke: smoke testcasesuser: user testcases
run.py
import os
import time
import pytestif __name__ == "__main__":pytest.main()time.sleep(1)os.system("allure generate outputs/logs ‐o outputs/reports --clean")
相关文章:

07 APP 自动化- appium+pytest+allure框架封装
文章目录 一、PO二、代码简单实现项目框架预览:base_page.pydir_config.pyget_data.pylogger.pystart_session.pyconfig.yamlkey_code.yamllaunch_page_loc.pylogin_page_loc.pylaunch_page.pylogin_page.pytest_login.pypytest.inirun.py 一、PO PO 分为四层 &…...
Postgresql常规SQL语句操作
目录 一、数据库与对象管理 二、数据操作 (CRUD) 三、查询优化与执行计划分析 四、事务控制 五、数据类型与高级特性应用 六、系统查询与维护 研发中的重要注意事项 在 PostgreSQL 研发中,以下这些 SQL 应用是极其常见且核心的操作,涵盖了数据库设…...
智能合约安全漏洞解析:从 Reentrancy 到 Integer Overflow
目录 🌀 Reentrancy(重入攻击) 原理解析 典型案例:The DAO 攻击事件 漏洞示例 防范措施 🔢 Integer Overflow(整数溢出) 原理解析 漏洞示例 防范措施 🛡️ 总结与建议 随着…...

英国2025年战略防御评估报告:网络与电磁域成现代战争核心
英国 2025 年战略防御评估 (SDR) 详细制定了一项计划,通过加强使用网络、人工智能和数字战争来整合其军事防御和进攻能力。 与美国一样,英国也被认为(尽管未被公开证实)会开展进攻性网络行动,甚至针对盟友。斯诺登泄露…...

基于QPSK调制解调+Polar编译码(SCL译码)的matlab性能仿真,并对比BPSK
目录 1.引言 2.算法仿真效果演示 3.数据集格式或算法参数简介 4.MATLAB核心程序 5.算法涉及理论知识概要 6.参考文献 7.完整算法代码文件获得 1.引言 Polar码由土耳其教授Erdal Arikan于2008年提出,是第一种被严格证明可以达到香农极限的构造性编码方法。其核…...
go语言学习 第5章:函数
第5章:函数 函数是编程中不可或缺的一部分,它封装了一段可重复使用的代码,用于执行特定的任务。在Go语言中,函数同样扮演着重要的角色。本章将详细介绍Go语言中函数的定义、调用、参数传递、返回值处理以及一些高级特性ÿ…...
Qt Quick快速入门笔记
Qt Quick快速入门笔记 基本的程序结构int main(int argc, char *argv[]) { #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endifQGuiApplication app(argc, argv);QQmlApplicationEngine engine;const QUrl ur…...
《波段操盘实战技法》速读笔记
文章目录 书籍信息概览实战八法波段见顶信号中长线大顶形态投资理念 书籍信息 书名:《波段操盘实战技法》 作者:何瑞东 概览 实战八法 投资理念和投资理论概述:波段操作的核心是通过捕捉股价波动中的趋势性机会,结合技术分析与…...

Glide NoResultEncoderAvailableException异常解决
首先将解决方法提出来:缓存策略DiskCacheStrategy.DATA。 使用Glide加载图片,版本是4.15.0,有天发现无法显示gif图片,原始代码如下: Glide.with(context).load(本地资源路径).diskCacheStrategy(DiskCacheStrategy.A…...
工厂模式与多态结合
工厂模式与多态的结合是平台化项目中实现灵活架构的核心技术之一。这种组合能够创建可扩展、易维护的系统架构。 多态(Polymorphism)指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。 例子1: public abstract class Pay…...

无人机巡检智能边缘计算终端技术方案——基于EFISH-SCB-RK3588工控机/SAIL-RK3588核心板的国产化替代方案
一、方案核心价值 实时AI处理:6TOPS NPU实现无人机影像的实时缺陷检测(延迟<50ms)全国产化:芯片、操作系统、算法工具链100%自主可控极端环境适配:-40℃~85℃稳定运行,IP65防护等…...
相机--相机成像原理和基础概念
教程 成像原理 基础概念 焦距(物理焦距) 镜头的光学中心到感光元件之间的距离,用f表示,单位:mm;。 像素焦距 相机内参矩阵中的 fx 和 fy 是将物理焦距转换到像素坐标系的产物,可能不同。…...

2025-0604学习记录17——文献阅读与分享(2)
最近不是失踪了!也不是弃坑了...这不是马上要毕业了嘛!所以最近在忙毕业论文答辩、毕业去向填报、户档去向填报等等,事情太多了,没顾得上博客。现在这些事基本上都解决完了,也有时间静下心来写写文字了~ 想要写的内容…...

图解浏览器多进程渲染:从DNS到GPU合成的完整旅程
目录 浅谈浏览器进程 浏览器进程架构的演化 进程和线程关系图示 进程(Process) 线程(Thread) 协程(Coroutine) 进程&线程&协程核心对比 单进程和多进程浏览器 单进程浏览器编辑 单进程…...

【计算机网络】第3章:传输层—TCP 拥塞控制
目录 一、PPT 二、总结 TCP 拥塞控制详解 ⭐ 核心机制与算法 1. 慢启动(Slow Start) 2. 拥塞避免(Congestion Avoidance) 3. 快速重传(Fast Retransmit) 4. 快速恢复(Fast Recovery&…...

idea不识别lombok---实体类报没有getter方法
介绍 本篇文章,主要讲idea引入lombok后,在实体类中加注解Data,在项目启动的时候,编译不通过,报错xxx.java没有getXxxx()方法。 原因有以下几种 1. idea没有开启lombok插件 2. 使用idea-2023…...
【Hive入门】
之前实习写的笔记,上传留个备份。 1. 使用docker-compose快速搭建Hive集群 使用docker快速配置Hive环境 拉取镜像 2. Hive数据类型 隐式转换:窄的可以向宽的转换显式转换:cast 3. Hive读写文件 SerDe:序列化(对象转为字节码…...
亚马逊站内信规则2025年重大更新:避坑指南与合规策略
亚马逊近期对Buyer-Seller Messaging(买家-卖家站内信)规则进行了显著收紧,明确将一些曾经的“灰色操作”列为违规。违规操作轻则收到警告,重则导致账户暂停或绩效受限。本文为您全面解析本次规则更新的核心要点、背后逻辑&#x…...
01 - AI 时代的操作系统课 [2025 南京大学操作系统原理]
01 - AI 时代的操作系统课 [2025 南京大学操作系统原理] [00:00:00]-[D:\movie\南京大学操作系统\01-AI时代的操作系统课[2025南京大学操作系统原理].mp4] 大家好!我是姜艳艳,来自南京大学计算机软件研究所。今天我们开启《操作系统原理》的第一课&…...
数组1 day7
六:数组 一:数据类型 1.int a[10] //想要知道一个标识符对应的数据类型,去掉标识符,剩下就是它对应的数据类型 //eg:a所谓代表的类型,就是int[10]这种类型(是一个数组,包含10个…...

SAP学习笔记 - 开发15 - 前端Fiori开发 Boostrap,Controls,MVC(Model,View,Controller),Modules
上一章讲了Fiori开发的准备,以及宇宙至简之HelloWorld。 SAP学习笔记 - 开发14 - 前端Fiori开发 HelloWorld-CSDN博客 本章继续学习 Fiori 开发的知识: Bootstrap,Controls,MVC(Model,View,Controller&a…...
Redis中的过期策略与内存淘汰策略
因为Redis是纯内存操作,所以在Redis中创建的键一般都会带有过期时间,以此来保证内存中存储数据的时效性。这篇文章我们就来讲解一下Redis中的过期策略与内存淘汰策略。 如何设置Redis中键的过期时间? Redis提供了4个命令来设置键的过期时间&…...

基于SDN环境下的DDoS异常攻击的检测与缓解
参考以下两篇博客,最后成功: 基于SDN的DDoS攻击检测和防御方法_基于sdn的ddos攻击检测与防御-CSDN博客 利用mininet模拟SDN架构并进行DDoS攻击与防御模拟(Ryumininetsflowpostman)_mininet模拟dos攻击-CSDN博客 需求 H2 模拟f…...
HarmonyOS 实战:给笔记应用加防截图水印
最近在做笔记类应用时,遇到一个头疼的需求:防止用户内容被非法截图传播。思来想去,加水印是个直接有效的方案。研究了 HarmonyOS 的开发文档后,发现用 Canvas 配合布局组件能轻松实现动态水印效果。今天就来聊聊如何给笔记页面加上…...

如何轻松地将文件从 PC 传输到 iPhone?
传统上,您可以使用 iTunes 将文件从 PC 传输到 iPhone,但现在,使用 iTunes 已不再是唯一的选择。现在有多种不同且有效的方法可以帮助您传输文件。在今天的指南中,您可以找到 8 种使用或不使用 iTunes 传输文件的方法,…...
前端面试二之运算符与表达式
目录 1.JavaScript 中的 和 运算符 2.|| (逻辑或) 运算符 与 ES6 默认参数的区别 与 ?? (空值合并运算符) 的区别 3.?.(可选链)运算符 (1). 安全访问深层嵌套属性 (2). 安全调用可能不存在的函数 (3). 安全访问数组元素 4.展开运算符 (..…...
【运维实战】使用Nvm配置多Node.js环境!
背景 新项目 使用Node.js-v16.17.1旧项目 使用Node.js- v14.18.0 【且依赖于node-saas模块,根据 node-sass 的官方文档,目前最新版本的 node-sass(即 v5.0.0)支持的 Node.js 版本范围是 Node.js 10.x、Node.js 12.x、Node.js 14.…...

Bresenham算法
一 Bresenham 绘直线 使用 Bresenham 算法,可以在显示器上绘制一直线段。该算法主要思想如下: 1 给出直线段上两个端点 ,根据端点求出直线在X,Y方向上变化速率 ; 2 当 时,X 方向上变化速率快于 Y 方向上变化速率&am…...

【从GEO数据库批量下载数据】
从GEO数据库批量下载数据 1:进入GEO DataSets拿到所需要下载的数据的srr.list,上传到linux, 就可以使用prefetch这个函数来下载 2:操作步骤如下: conda 安装sra-tools conda create -n sra-env -c bioconda -c co…...

day 44
使用DenseNet预训练模型对cifar10数据集进行训练 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms, models from torch.utils.data import DataLoader import matplotlib.pyplot as plt import os# 设置中文字体…...