做自己的英雄 3 долоо хоног өмнө
commit
7b159ac546
100 өөрчлөгдсөн 1310 нэмэгдсэн , 0 устгасан
  1. 3 0
      .idea/.gitignore
  2. 6 0
      .idea/inspectionProfiles/Project_Default.xml
  3. 6 0
      .idea/inspectionProfiles/profiles_settings.xml
  4. 4 0
      .idea/misc.xml
  5. 8 0
      .idea/modules.xml
  6. 16 0
      .idea/web_pom_project.iml
  7. BIN
      __pycache__/conftest.cpython-310-pytest-8.4.1.pyc
  8. BIN
      __pycache__/conftest.cpython-39-pytest-7.1.2.pyc
  9. 85 0
      base/WebKeys.py
  10. 0 0
      base/__init__.py
  11. BIN
      base/__pycache__/WebKeys.cpython-310.pyc
  12. BIN
      base/__pycache__/WebKeys.cpython-39.pyc
  13. BIN
      base/__pycache__/__init__.cpython-310.pyc
  14. BIN
      base/__pycache__/__init__.cpython-39.pyc
  15. 0 0
      case_test/__init__.py
  16. 41 0
      case_test/ccomment_case.py
  17. 77 0
      case_test/read_case.py
  18. 0 0
      common/__init__.py
  19. BIN
      common/__pycache__/__init__.cpython-310.pyc
  20. BIN
      common/__pycache__/__init__.cpython-39.pyc
  21. BIN
      common/__pycache__/excel_read.cpython-310.pyc
  22. BIN
      common/__pycache__/excel_read.cpython-39.pyc
  23. 35 0
      common/excel_read.py
  24. 81 0
      conftest.py
  25. 0 0
      data/__init__.py
  26. 3 0
      data/loc_data.py
  27. BIN
      data/登陆账号.xlsx
  28. BIN
      data/登陆账号失败.xlsx
  29. BIN
      data/登陆账号成功.xlsx
  30. 13 0
      locator/AllPages.py
  31. 0 0
      locator/__init__.py
  32. BIN
      locator/__pycache__/AllPages.cpython-310.pyc
  33. BIN
      locator/__pycache__/AllPages.cpython-39.pyc
  34. BIN
      locator/__pycache__/__init__.cpython-310.pyc
  35. BIN
      locator/__pycache__/__init__.cpython-39.pyc
  36. 16 0
      main.py
  37. 0 0
      page/__init__.py
  38. BIN
      page/__pycache__/__init__.cpython-310.pyc
  39. BIN
      page/__pycache__/__init__.cpython-39.pyc
  40. BIN
      page/__pycache__/commentPage.cpython-310.pyc
  41. BIN
      page/__pycache__/commentPage.cpython-39.pyc
  42. BIN
      page/__pycache__/createChapterLogic.cpython-39.pyc
  43. BIN
      page/__pycache__/delChapterPage.cpython-39.pyc
  44. BIN
      page/__pycache__/loginPage.cpython-310.pyc
  45. BIN
      page/__pycache__/loginPage.cpython-39.pyc
  46. BIN
      page/__pycache__/searchPage.cpython-310.pyc
  47. BIN
      page/__pycache__/searchPage.cpython-39.pyc
  48. 29 0
      page/commentPage.py
  49. 48 0
      page/createChapterLogic.py
  50. 16 0
      page/delChapterPage.py
  51. 40 0
      page/loginPage.py
  52. 31 0
      page/searchPage.py
  53. 1 0
      report/app.js
  54. BIN
      report/data/attachments/1eb13e638849764a.png
  55. BIN
      report/data/attachments/443479feaca11e2e.png
  56. BIN
      report/data/attachments/5be91d7358a32712.png
  57. BIN
      report/data/attachments/93aec96785e095cb.png
  58. BIN
      report/data/attachments/c8098addea7496.png
  59. BIN
      report/data/attachments/e0c37c0f6bd3e144.png
  60. BIN
      report/data/attachments/e2c5d016592d42be.png
  61. 5 0
      report/data/behaviors.csv
  62. 0 0
      report/data/behaviors.json
  63. 3 0
      report/data/categories.csv
  64. 0 0
      report/data/categories.json
  65. 0 0
      report/data/packages.json
  66. 8 0
      report/data/suites.csv
  67. 0 0
      report/data/suites.json
  68. 0 0
      report/data/test-cases/1fb90c11fcde3133.json
  69. 0 0
      report/data/test-cases/2676f1432c30332d.json
  70. 0 0
      report/data/test-cases/30fcaadeb0b2b005.json
  71. 0 0
      report/data/test-cases/7e597899afd88891.json
  72. 0 0
      report/data/test-cases/93232cb4c6c9e5e2.json
  73. 0 0
      report/data/test-cases/b5b0ec12a20631c7.json
  74. 0 0
      report/data/test-cases/b5b354cd32adbc50.json
  75. 0 0
      report/data/timeline.json
  76. 15 0
      report/export/influxDbData.txt
  77. 10 0
      report/export/mail.html
  78. 15 0
      report/export/prometheusData.txt
  79. BIN
      report/favicon.ico
  80. 1 0
      report/history/categories-trend.json
  81. 1 0
      report/history/duration-trend.json
  82. 1 0
      report/history/history-trend.json
  83. 0 0
      report/history/history.json
  84. 1 0
      report/history/retry-trend.json
  85. 34 0
      report/index.html
  86. 262 0
      report/plugin/behaviors/index.js
  87. 152 0
      report/plugin/packages/index.js
  88. 200 0
      report/plugin/screen-diff/index.js
  89. 30 0
      report/plugin/screen-diff/styles.css
  90. 3 0
      report/styles.css
  91. 1 0
      report/widgets/behaviors.json
  92. 1 0
      report/widgets/categories-trend.json
  93. 1 0
      report/widgets/categories.json
  94. 1 0
      report/widgets/duration-trend.json
  95. 1 0
      report/widgets/duration.json
  96. 1 0
      report/widgets/environment.json
  97. 1 0
      report/widgets/executors.json
  98. 1 0
      report/widgets/history-trend.json
  99. 1 0
      report/widgets/launch.json
  100. 1 0
      report/widgets/retry-trend.json

+ 3 - 0
.idea/.gitignore

@@ -0,0 +1,3 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml

+ 6 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="LanguageDetectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+  </profile>
+</component>

+ 6 - 0
.idea/inspectionProfiles/profiles_settings.xml

@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="USE_PROJECT_PROFILE" value="false" />
+    <version value="1.0" />
+  </settings>
+</component>

+ 4 - 0
.idea/misc.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (web_pom_project2)" project-jdk-type="Python SDK" />
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/web_pom_project.iml" filepath="$PROJECT_DIR$/.idea/web_pom_project.iml" />
+    </modules>
+  </component>
+</project>

+ 16 - 0
.idea/web_pom_project.iml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PYTHON_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/venv" />
+    </content>
+    <orderEntry type="jdk" jdkName="Python 3.10 (web_pom_project2)" jdkType="Python SDK" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+  <component name="PackageRequirementsSettings">
+    <option name="requirementsPath" value="" />
+  </component>
+  <component name="TestRunnerService">
+    <option name="PROJECT_TEST_RUNNER" value="Unittests" />
+  </component>
+</module>

BIN
__pycache__/conftest.cpython-310-pytest-8.4.1.pyc


BIN
__pycache__/conftest.cpython-39-pytest-7.1.2.pyc


+ 85 - 0
base/WebKeys.py

@@ -0,0 +1,85 @@
+import allure
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.wait import WebDriverWait
+from selenium.webdriver.support import expected_conditions as ec
+
+class WebKeys:
+    # 浏览器  __init__ 初始化函数  只要实例化WebKeys  就会走到init里面打开浏览器
+    # webdriver.chorme 外面创建
+    def __init__(self,driver):
+        self.driver=driver
+        self.wait=WebDriverWait(self.driver, 10)
+
+    # 访问不同的网址  函数传参
+    @allure.step("访问")
+    def open(self,url):
+        self.driver.get(url)
+
+     # 我不知道给哪个元素加边框  1 不能2
+
+    def locator_station(self, element):
+        self.driver.execute_script(
+            "arguments[0].setAttribute('style',arguments[1]);",
+            element,
+            "border: 2px solid green;"  # 边框,green绿色
+        )
+
+    # 元素定位 传两个参数  可以用显示等待定位元素
+    # visibility_of_element_located 传元组 (By.id,xxx)
+    # 显示等待+定位  找到元素之后 要返回出去
+    # 为什么要返回出去 是因为后面要马上操作它 调用的时候 找到元素之后 才能进行点击进行输入
+    # 找到元素之后有边框  加进来封装
+    @allure.step("元素定位")
+    def locator_with_wait(self,name,value):
+        # self.driver.find_element(name,value).send_keys()
+        # el=self.driver.find_element(name,value)
+        # el.send_keys()
+        locator=(name,value)
+        el=self.wait.until(ec.visibility_of_element_located(locator))
+        self.locator_station(el)
+        return el
+
+    @allure.step("多元素定位")
+    def locators_with_wait(self, name, value):
+        locator = (name, value)
+        el = self.wait.until(ec.visibility_of_all_elements_located(locator))
+        return el
+
+    @allure.step("点击")
+    def on_click(self,name,value):
+        self.locator_with_wait(name, value).click()
+
+
+    # 窗口切换  项目中基础操作 作家管理
+    @allure.step("窗口切换")
+    def change_window(self,n):
+        # 获取所有的窗口  获取所有的句柄
+        handles=self.driver.window_handles
+        # 切换窗口  拿下标  到底拿哪个下标
+        self.driver.switch_to.window(handles[n])
+
+    # 文本断言  登陆的时候
+    # locator = ("partial link text", "章节管理")
+    # wait.until(ec.visibility_of_element_located(locator)).click()
+
+    # 为什么写返回是因为我后面需要拿这个值进行断言  返回
+    # 获得结果
+    @allure.step("获得返回结果")
+    def text_wait(self,name,value,txt):
+        el=(name,value)
+        result=self.wait.until(ec.text_to_be_present_in_element(el,txt))
+        return result
+
+#     获得文本内容结果
+    @allure.step("获得返回文本")
+    def get_text(self,name,value):
+        el = (name, value)
+        # result = self.wait.until(ec.visibility_of_element_located(el)).text
+        result = self.wait.until(
+            lambda x: x.find_element(*el).text
+        )
+        return result
+
+    # lamda
+#     断言文本内容
+

+ 0 - 0
base/__init__.py


BIN
base/__pycache__/WebKeys.cpython-310.pyc


BIN
base/__pycache__/WebKeys.cpython-39.pyc


BIN
base/__pycache__/__init__.cpython-310.pyc


BIN
base/__pycache__/__init__.cpython-39.pyc


+ 0 - 0
case_test/__init__.py


+ 41 - 0
case_test/ccomment_case.py

@@ -0,0 +1,41 @@
+import time
+
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.wait import WebDriverWait
+from selenium.webdriver.support import expected_conditions as ec
+
+driver = webdriver.Chrome()  # 实例化浏览器对象
+driver.maximize_window()
+wait = WebDriverWait(driver, 5)  # 实例化等待对象
+login_url = "http://120.25.127.201:18001/user/login.html"
+driver.get(login_url)  # 在新的窗口打开url
+username="15574113907"
+password="123456"
+wait.until(ec.presence_of_element_located((By.XPATH, "//input[@id='txtUName']"))).send_keys(username)
+wait.until(ec.presence_of_element_located((By.XPATH, "//input[@id='txtPassword']"))).send_keys(password)
+wait.until(ec.presence_of_element_located((By.XPATH, "//input[@id='btnLogin']"))).click()
+# 根据username出现在首页作为等待条件,确保首页正常出现
+wait.until(ec.text_to_be_present_in_element((By.LINK_TEXT,username),username))
+
+
+locator = ("link text", "云上夕轮")
+wait.until((ec.visibility_of_element_located(locator))).click()
+
+# 显性等待发表评论按钮,并进行点击
+locator = ("link text", "发表评论")
+wait.until((ec.visibility_of_element_located(locator))).click()
+
+# 显性等待评论输入框,并进行输入
+content = f"发表评论-{str(int(time.time()))}"
+locator = ("id", "txtComment")
+wait.until((ec.visibility_of_element_located(locator))).send_keys(content)
+
+# 显性等待发表按钮,并进行点击
+locator = ("css selector", ".fr>.btn_ora")
+wait.until((ec.visibility_of_element_located(locator))).click()
+
+# 显性等待评论提示数据
+locator = ("css selector", ".layui-layer-content")
+ele_text = wait.until((ec.visibility_of_element_located(locator))).text
+assert "评价成功" in ele_text, "评论失败,提示框中的内容与预期不符"

+ 77 - 0
case_test/read_case.py

@@ -0,0 +1,77 @@
+import time
+
+import faker
+from selenium import webdriver
+from selenium.common import TimeoutException
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.wait import WebDriverWait
+from selenium.webdriver.support import expected_conditions as ec
+
+# 登陆的账号,注册了作者,发布过小说
+driver = webdriver.Chrome()  # 实例化浏览器对象
+driver.maximize_window()
+wait = WebDriverWait(driver, 5)  # 实例化等待对象
+login_url = "http://120.25.127.201:18001/user/login.html"
+driver.get(login_url)  # 在新的窗口打开url
+username="15574113907"
+password="123456"
+el=wait.until(ec.presence_of_element_located((By.XPATH, "//input[@id='txtUName']")))
+el.send_keys(username)
+wait.until(ec.presence_of_element_located((By.XPATH, "//input[@id='txtPassword']"))).send_keys(password)
+wait.until(ec.presence_of_element_located((By.XPATH, "//input[@id='btnLogin']"))).click()
+# 根据username出现在首页作为等待条件,确保首页正常出现  你的元素的值是 15574113907  true false
+wait.until(ec.text_to_be_present_in_element((By.LINK_TEXT,username),username))
+
+# 显性等待作者专区元素并进行点击
+locator = ("link text", "作家专区")
+wait.until(ec.visibility_of_element_located(locator)).click()
+
+# 切换windows窗口
+handle = driver.window_handles[-1]  # 获取窗口句柄
+driver.switch_to.window(handle)  # 切换窗口
+
+# 点击章节管理
+# 显性等待章节管理元素出现并进行点击
+locator = ("partial link text", "章节管理")
+wait.until(ec.visibility_of_element_located(locator)).click()
+
+# 切换窗口句柄
+driver.switch_to.window(driver.window_handles[-1])
+
+try:
+    # 如果有数据,点击某个元素
+    locator = ("xpath", "//*[@id='hasContentDiv']/div[1]/div/a")
+    wait.until(ec.visibility_of_element_located(locator)).click()
+except TimeoutException:
+    # 如果没有数据,点击另一个元素
+    locator = ("xpath", "//*[@id='noContentDiv']/div/a")
+    wait.until(ec.visibility_of_element_located(locator)).click()
+except Exception:
+    print("章节管理元素未找到")
+
+
+# 显性等待章节名元素出现并进行输入从操作
+locator = ("id", "bookIndex")
+wait.until(ec.visibility_of_element_located(locator)).send_keys(f"章节名-{str(int(time.time()))}")
+
+# 显性等待章节内容元素出现并进行输入从操作
+locator = ("id", "bookContent")
+wait.until(ec.visibility_of_element_located(locator)).send_keys(f"章节内容-{str(int(time.time()))}")
+
+# 显性等待收费元素出现并点击
+locator = ("css selector", '[name="isVip"][value="1"]')
+wait.until(ec.visibility_of_element_located(locator)).click()
+
+current_url = driver.current_url
+# 显性等待提交元素出现并进行点击
+locator = ("id", "btnRegister")
+wait.until(ec.visibility_of_element_located(locator)).click()
+
+time.sleep(5)
+driver.quit()
+
+# driver.execute_script(
+#             "arguments[0].setAttribute('style',arguments[1]);",
+#             el,
+#             "border: 2px solid green;"  # 边框,green绿色
+#         )

+ 0 - 0
common/__init__.py


BIN
common/__pycache__/__init__.cpython-310.pyc


BIN
common/__pycache__/__init__.cpython-39.pyc


BIN
common/__pycache__/excel_read.cpython-310.pyc


BIN
common/__pycache__/excel_read.cpython-39.pyc


+ 35 - 0
common/excel_read.py

@@ -0,0 +1,35 @@
+# 读取excel数据  openpyxl的方式去读取
+# 安装pip install openpyxl -i 镜像
+import openpyxl
+
+# 加载excel 你的excel在哪  ../  ./ 看你的运行文件在哪?
+# 一套房子  base房间  data  common房间  main test在客厅
+# common房间运行找 data登陆账号.xlsx
+# 找到excel
+def load_excel(file_name):
+    # 加载excel
+    workbook=openpyxl.load_workbook(file_name)
+    # 找表单  Sheet1  Sheet2
+    sheet=workbook['Sheet1']
+    # 拿表单的值 很多值  循环拿里面的值  sheet.iter_rows 行
+    # min_row=2 从第二行开始  values_only=True 获取单元格的值
+    data=[]
+    for i in sheet.iter_rows(min_row=2,values_only=True):
+        # print(row)
+        # (1,15574113900, 123456, True)
+        # (2,15574113901, 123457, False)
+        # (3,15574113902, 123458, False)
+        # 数据还要做处理 元组 需要列表  i[1] 第一列拿出来   i[2] 第二列拿出来
+        account=i[1]
+        password=i[2]
+        assertion=i[3]
+        data.append((account,password,assertion))
+    return data
+
+    # [(15574113900, 123456, True), (15574113901, 123457, False), (15574113902, 123458, False)]
+    # print(data)
+
+# 测试这个方法  如果不写main  文件
+if __name__ == '__main__':
+    d=load_excel("../data/登陆账号.xlsx")
+    print(d)

+ 81 - 0
conftest.py

@@ -0,0 +1,81 @@
+import allure
+import pytest
+from selenium import webdriver
+
+@pytest.fixture(scope="function")
+def driver():
+    driver=webdriver.Chrome()
+    driver.maximize_window()
+    yield driver
+    driver.quit()
+
+# 截图功能
+# hook装饰器
+# pytest_runtest_makereport 函数名不要改动
+# item 当前的测试用例  call用例的结果
+# yield暂停
+# outcome.get_result()获得结果
+# if report.when == "call" and (report.failed or report.passed): 检查测试用例阶段
+# 用例成功或者失败
+# driver = item.funcargs.get('driver')获得浏览器
+@pytest.hookimpl(hookwrapper=True)
+def pytest_runtest_makereport(item, call):
+    outcome = yield
+    report=outcome.get_result()
+    if report.when == "call" and (report.failed or report.passed):
+        driver = item.funcargs.get('driver')
+        if driver:
+            if report.failed:
+                with allure.step("添加失败的截图 ---> "):
+                    allure.attach(driver.get_screenshot_as_png(), "失败的截图", allure.attachment_type.PNG)
+            elif report.passed:
+                with allure.step("添加成功的截图 ---> "):
+                    allure.attach(driver.get_screenshot_as_png(), "成功的截图", allure.attachment_type.PNG)
+
+
+
+
+
+
+# 不要把什么方法都丢到这里来
+
+# 有 1  没有 2
+# 用例有关  用例重跑 用例顺序 pytest的管理用例
+# 生成allure测试报告
+
+# base 文件 经常要做的操作 补充很多东西  没有固定的
+# page文件夹 用例 页面 很多人有不同的写法 页面可细  可粗 pom很灵活根据自己想要的内容去写
+# 用例 断言 可以在封装 不是只有assert 预期结果(外部传来的文件)==实际结果 方法
+# 数据 data文件夹 可以根据的操作去写
+# 总而言之 pom很灵活
+
+# 打开浏览器 测完这条用例 关闭浏览器
+# 搜索书籍  打开浏览器 测完这条用例  关闭浏览器
+# 评论  打开浏览器 评论  关闭浏览器
+# 打开浏览器 创建管理  关闭浏览器
+
+# 登陆--点击作家专区--申请作者--发布小说--章节管理--新建章节--关闭浏览器
+
+# 作家专区页面 zuozhePage
+# 申请作者页面
+# -发布小说
+# 章节管理-新建章节
+# 登陆  zuozhePage().方法  申请.方法()  小说.方法()  章节管理.方法() --关闭浏览器
+
+# 搜索页查询
+# 评论页 查询
+# 登陆  搜索页查询  评论页(访问评论页  open()搜索)
+# pom按照自己的思维去写 写出来 并且单独的用例执行 流程用例执行
+
+# 生成Allure测试报告  前提操作:
+
+# 1.jdk安装配置好  下载安装下  .exe的程序  一路下一步  在C盘找到你的安装  进行配置
+# 环境变量配置:
+# 找到path目录 新建到 C:\Program Files\Java\jdk1.8.0_211\bin
+# 确定有没有java  在cmd里面输入java -version
+
+# 2.下载commandline包 并且配置环境变量
+
+# 3.安装allure-pytest库   pip install allure-pytest -i https://mirrors.aliyun.com/pypi/simple/
+
+# 重启pycharm

+ 0 - 0
data/__init__.py


+ 3 - 0
data/loc_data.py

@@ -0,0 +1,3 @@
+from selenium.webdriver.common.by import By
+
+USERNAME_LOC=By.XPATH,"//input[@id='txtUName']"

BIN
data/登陆账号.xlsx


BIN
data/登陆账号失败.xlsx


BIN
data/登陆账号成功.xlsx


+ 13 - 0
locator/AllPages.py

@@ -0,0 +1,13 @@
+from selenium.webdriver.common.by import By
+
+# 登陆的元素定位
+# 用户名
+username_loc=[By.XPATH,"//input[@id='txtUName']"]
+# 用密码
+password_loc=[By.XPATH,"//input[@id='txtPassword']"]
+# 登陆
+loginbtn_loc=[By.XPATH, "//input[@id='btnLogin']"]
+
+login_assert_true=[By.CSS_SELECTOR,".mr15"]
+
+login_assert_text=[By.XPATH,'//*[@id="LabErr"]']

+ 0 - 0
locator/__init__.py


BIN
locator/__pycache__/AllPages.cpython-310.pyc


BIN
locator/__pycache__/AllPages.cpython-39.pyc


BIN
locator/__pycache__/__init__.cpython-310.pyc


BIN
locator/__pycache__/__init__.cpython-39.pyc


+ 16 - 0
main.py

@@ -0,0 +1,16 @@
+# 这是一个示例 Python 脚本。
+
+# 按 Shift+F10 执行或将其替换为您的代码。
+# 按 双击 Shift 在所有地方搜索类、文件、工具窗口、操作和设置。
+
+
+def print_hi(name):
+    # 在下面的代码行中使用断点来调试脚本。
+    print(f'Hi, {name}')  # 按 Ctrl+F8 切换断点。
+
+
+# 按间距中的绿色按钮以运行脚本。
+if __name__ == '__main__':
+    print_hi('PyCharm')
+
+# 访问 https://www.jetbrains.com/help/pycharm/ 获取 PyCharm 帮助

+ 0 - 0
page/__init__.py


BIN
page/__pycache__/__init__.cpython-310.pyc


BIN
page/__pycache__/__init__.cpython-39.pyc


BIN
page/__pycache__/commentPage.cpython-310.pyc


BIN
page/__pycache__/commentPage.cpython-39.pyc


BIN
page/__pycache__/createChapterLogic.cpython-39.pyc


BIN
page/__pycache__/delChapterPage.cpython-39.pyc


BIN
page/__pycache__/loginPage.cpython-310.pyc


BIN
page/__pycache__/loginPage.cpython-39.pyc


BIN
page/__pycache__/searchPage.cpython-310.pyc


BIN
page/__pycache__/searchPage.cpython-39.pyc


+ 29 - 0
page/commentPage.py

@@ -0,0 +1,29 @@
+import time
+
+import allure
+
+from base.WebKeys import WebKeys
+
+
+class CommentPage(WebKeys):
+    def comment(self,book):
+        # 找某一本书进行点击
+        self.locator_with_wait("link text",book).click()
+        # 显性等待发表评论按钮,并进行点击
+        locator = ("link text", "发表评论")
+        self.locator_with_wait(*locator).click()
+
+        # 显性等待评论输入框,并进行输入
+        content = f"发表评论-{str(int(time.time()))}"
+        locator = ("id", "txtComment")
+        self.locator_with_wait(*locator).send_keys(content)
+
+        # 显性等待发表按钮,并进行点击
+        locator = ("css selector", ".fr>.btn_ora")
+        self.locator_with_wait(*locator).click()
+
+
+    def get_result_text(self):
+        locator = ("css selector", ".layui-layer-content")
+        result=self.get_text(*locator)
+        return result

+ 48 - 0
page/createChapterLogic.py

@@ -0,0 +1,48 @@
+import time
+
+from selenium.common import TimeoutException
+
+from base.WebKeys import WebKeys
+
+
+class CreateChapterPage(WebKeys):
+    def create_Chapter(self):
+        # 显性等待作者专区元素并进行点击
+        locator = ("link text", "作家专区")
+        self.locator_with_wait(*locator).click()
+        #切换windows窗口
+        self.change_window(-1)
+        # 点击章节管理
+        locator = ("partial link text", "章节管理")
+        self.locator_with_wait(*locator).click()
+        # 切换窗口句柄
+        self.change_window(-1)
+
+        try:
+            # 如果有数据,点击某个元素
+            locator = ("xpath", "//*[@id='hasContentDiv']/div[1]/div/a")
+            self.locator_with_wait(*locator).click()
+        except TimeoutException:
+            # 如果没有数据,点击另一个元素
+            locator = ("xpath", "//*[@id='noContentDiv']/div/a")
+            self.locator_with_wait(*locator).click()
+        except Exception:
+            print("章节管理元素未找到")
+
+        # 显性等待章节名元素出现并进行输入从操作
+        locator = ("id", "bookIndex")
+        bookname=self.locator_with_wait(*locator).send_keys(f"章节名-{str(int(time.time()))}")
+
+        # 显性等待章节内容元素出现并进行输入从操作
+        locator = ("id", "bookContent")
+        self.locator_with_wait(*locator).send_keys(f"章节内容-{str(int(time.time()))}")
+
+        # 显性等待收费元素出现并点击
+        locator = ("css selector", '[name="isVip"][value="1"]')
+        self.locator_with_wait(*locator).click()
+
+        # 显性等待提交元素出现并进行点击
+        locator = ("id", "btnRegister")
+        self.locator_with_wait(*locator).click()
+        return bookname
+

+ 16 - 0
page/delChapterPage.py

@@ -0,0 +1,16 @@
+from selenium.webdriver.common.by import By
+
+from base.WebKeys import WebKeys
+
+
+class DelChapter(WebKeys):
+    def del_chapter(self):
+        self.locator_with_wait(By.XPATH,'//*[@class="book_list"]//td/a[2]').click()
+        self.locator_with_wait(By.LINK_TEXT,"确定").click()
+
+        # 找到所有的书
+        ele=self.locators_with_wait(By.XPATH,'//*[@id="bookList"]//td[1]')
+        name_list=[]
+        for i in ele:
+            name_list.append(i.text)
+        return name_list

+ 40 - 0
page/loginPage.py

@@ -0,0 +1,40 @@
+# 登陆相关的放在这里面 后面登陆功能改变了 在进行这个页面更改
+# 继承这个工具类 ,直接调用这个工具类的方法 直接拿工具使用
+import allure
+from selenium.webdriver.common.by import By
+from selenium import webdriver
+from base.WebKeys import WebKeys
+
+# 在page页面一般不进行断言  测试用例才进行断言
+# 定位元素  用例里面 专门来个文件夹 保存元素定位的
+from locator.AllPages import *
+
+
+class LoginPage(WebKeys):
+    def login(self,username,password):
+        with allure.step("访问浏览器"):
+            self.open("http://novel.hctestedu.com/user/login.html")
+        with allure.step(f"输入用户名{username}"):
+            self.locator_with_wait(*username_loc).send_keys(username)
+        with allure.step("输入密码{}".format(password)):
+            self.locator_with_wait(*password_loc).send_keys(password)
+        with allure.step("点击登陆"):
+            self.on_click(*loginbtn_loc)
+
+    def get_result_true(self,txt):
+        result=self.text_wait(*login_assert_true,txt)
+        return result
+
+    def get_result_text(self):
+        result=self.get_text(*login_assert_text)
+        return result
+
+
+
+
+# 浏览器可以放在conftest里面是不是
+if __name__ == '__main__':
+    login_page=LoginPage(webdriver.Chrome())
+    sjmsg=login_page.login("15574113907","123456")
+    print(sjmsg)
+

+ 31 - 0
page/searchPage.py

@@ -0,0 +1,31 @@
+from selenium.webdriver.common.by import By
+
+from base.WebKeys import WebKeys
+
+
+class SearchPage(WebKeys):
+    def search_book(self,book,url=None):
+        if url:
+            self.open(url)
+        # 功能 搜索书籍的目的是为了知道 我的列表有没有这个本书
+        self.locator_with_wait(By.ID,"searchKey").send_keys(book)
+        self.locator_with_wait(By.ID,"btnSearch").click()
+
+        # 加载我的书籍列表
+        book_list=self.locator_with_wait(By.ID,"bookList")
+        # 获得书
+        book_rows=book_list.find_elements(By.TAG_NAME,"tr")
+        # 获得书名
+        book_found=[]
+        for row in book_rows:
+            # 循环了每一本书
+            book_name_ele=row.find_element(By.CSS_SELECTOR,".name>a")
+            book_name=book_name_ele.text
+            book_found.append(book_name)
+        return book_found
+        # 断言  我输入的书名 在不在 书籍列表
+
+
+# 作业:测试报告写全一点  page也可以多写点流程  pom实现一下代码 生成测试报告
+
+# 有问题1  没问题 2

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 1 - 0
report/app.js


BIN
report/data/attachments/1eb13e638849764a.png


BIN
report/data/attachments/443479feaca11e2e.png


BIN
report/data/attachments/5be91d7358a32712.png


BIN
report/data/attachments/93aec96785e095cb.png


BIN
report/data/attachments/c8098addea7496.png


BIN
report/data/attachments/e0c37c0f6bd3e144.png


BIN
report/data/attachments/e2c5d016592d42be.png


+ 5 - 0
report/data/behaviors.csv

@@ -0,0 +1,5 @@
+"BROKEN","EPIC","FAILED","FEATURE","PASSED","SKIPPED","STORY","UNKNOWN"
+"1","读书屋项目","1","登陆功能","1","0","这是登陆失败功能噢","0"
+"0","读书屋项目","0","登陆功能","2","0","这是登陆成功功能噢","0"
+"0","读书屋项目","0","评论功能","1","0","这是评论功能噢","0"
+"0","读书屋项目","0","搜索功能","1","0","这是搜索功能噢","0"

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
report/data/behaviors.json


+ 3 - 0
report/data/categories.csv

@@ -0,0 +1,3 @@
+"BROKEN","CATEGORY","FAILED","PASSED","SKIPPED","UNKNOWN"
+"0","Product defects","1","0","0","0"
+"1","Test defects","0","0","0","0"

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
report/data/categories.json


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
report/data/packages.json


+ 8 - 0
report/data/suites.csv

@@ -0,0 +1,8 @@
+"DESCRIPTION","DURATION IN MS","NAME","PARENT SUITE","START TIME","STATUS","STOP TIME","SUB SUITE","SUITE","TEST CLASS","TEST METHOD"
+"登陆失败,这是没问题的描述","1498","登陆失败用例,用户名为155742139,密码123457","test_case","Wed Jul 16 13:14:45 CST 2025","passed","Wed Jul 16 13:14:46 CST 2025","TestCase","login_test","",""
+"","3088","登陆成功用例,用户名为15574113900,密码123456","test_case","Wed Jul 16 13:14:21 CST 2025","passed","Wed Jul 16 13:14:24 CST 2025","TestCase","login_test","",""
+"登陆失败,这是没问题的描述","2235","登陆失败用例,用户名为15574413902,密码123458","test_case","Wed Jul 16 13:14:50 CST 2025","failed","Wed Jul 16 13:14:52 CST 2025","TestCase","login_test","",""
+"登陆失败,这是没问题的描述","11896","登陆失败用例,用户名为15574213900,密码123456","test_case","Wed Jul 16 13:14:28 CST 2025","broken","Wed Jul 16 13:14:40 CST 2025","TestCase","login_test","",""
+"","2881","登陆成功用例,用户名为15574113907,密码123456","test_case","Wed Jul 16 13:14:14 CST 2025","passed","Wed Jul 16 13:14:17 CST 2025","TestCase","login_test","",""
+"","8103","登陆-搜索书籍进行评论用例","test_case","Wed Jul 16 13:14:01 CST 2025","passed","Wed Jul 16 13:14:10 CST 2025","TestComment","comment_test","",""
+"","5510","登陆-搜索书籍用例","test_case","Wed Jul 16 13:14:56 CST 2025","passed","Wed Jul 16 13:15:02 CST 2025","TestSearch","search_test","",""

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
report/data/suites.json


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
report/data/test-cases/1fb90c11fcde3133.json


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
report/data/test-cases/2676f1432c30332d.json


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
report/data/test-cases/30fcaadeb0b2b005.json


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
report/data/test-cases/7e597899afd88891.json


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
report/data/test-cases/93232cb4c6c9e5e2.json


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
report/data/test-cases/b5b0ec12a20631c7.json


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
report/data/test-cases/b5b354cd32adbc50.json


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
report/data/timeline.json


+ 15 - 0
report/export/influxDbData.txt

@@ -0,0 +1,15 @@
+launch_status failed=1 1752642906000000000
+launch_status broken=1 1752642906000000000
+launch_status passed=5 1752642906000000000
+launch_status skipped=0 1752642906000000000
+launch_status unknown=0 1752642906000000000
+launch_time duration=60606 1752642906000000000
+launch_time min_duration=1498 1752642906000000000
+launch_time max_duration=11896 1752642906000000000
+launch_time sum_duration=35211 1752642906000000000
+launch_time start=1752642841897 1752642906000000000
+launch_time stop=1752642902503 1752642906000000000
+launch_problems product_defects=1 1752642906000000000
+launch_problems test_defects=1 1752642906000000000
+launch_retries retries=0 1752642906000000000
+launch_retries run=7 1752642906000000000

+ 10 - 0
report/export/mail.html

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>Allure Report summary mail</title>
+</head>
+<body>
+    Mail body
+</body>
+</html>

+ 15 - 0
report/export/prometheusData.txt

@@ -0,0 +1,15 @@
+launch_status_failed 1
+launch_status_broken 1
+launch_status_passed 5
+launch_status_skipped 0
+launch_status_unknown 0
+launch_time_duration 60606
+launch_time_min_duration 1498
+launch_time_max_duration 11896
+launch_time_sum_duration 35211
+launch_time_start 1752642841897
+launch_time_stop 1752642902503
+launch_problems_product_defects 1
+launch_problems_test_defects 1
+launch_retries_retries 0
+launch_retries_run 7

BIN
report/favicon.ico


+ 1 - 0
report/history/categories-trend.json

@@ -0,0 +1 @@
+[{"data":{"Product defects":1,"Test defects":1}}]

+ 1 - 0
report/history/duration-trend.json

@@ -0,0 +1 @@
+[{"data":{"duration":60606}}]

+ 1 - 0
report/history/history-trend.json

@@ -0,0 +1 @@
+[{"data":{"failed":1,"broken":1,"skipped":0,"passed":5,"unknown":0,"total":7}}]

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
report/history/history.json


+ 1 - 0
report/history/retry-trend.json

@@ -0,0 +1 @@
+[{"data":{"run":7,"retry":0}}]

+ 34 - 0
report/index.html

@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html dir="ltr" lang="en">
+<head>
+    <meta charset="utf-8">
+    <title>Allure Report</title>
+    <link rel="icon" href="favicon.ico">
+    <link rel="stylesheet" type="text/css" href="styles.css">
+    <link rel="stylesheet" type="text/css" href="plugin/screen-diff/styles.css">
+</head>
+<body>
+    <div id="alert"></div>
+    <div id="content">
+        <span class="spinner">
+            <span class="spinner__circle"></span>
+        </span>
+    </div>
+    <div id="popup"></div>
+    <script src="app.js"></script>
+    <script src="plugin/behaviors/index.js"></script>
+    <script src="plugin/packages/index.js"></script>
+    <script src="plugin/screen-diff/index.js"></script>
+    <script async src="https://www.googletagmanager.com/gtag/js?id=G-FVWC4GKEYS"></script>
+    <script>
+        window.dataLayer = window.dataLayer || [];
+        function gtag(){dataLayer.push(arguments);}
+        gtag('js', new Date());
+        gtag('config', 'G-FVWC4GKEYS', {
+          'allureVersion': '2.31.0',
+          'reportUuid': 'cd75fd26-b682-4975-b622-389ff1e88eff',
+          'single_file': false
+        });
+    </script>
+</body>
+</html>

+ 262 - 0
report/plugin/behaviors/index.js

@@ -0,0 +1,262 @@
+'use strict';
+
+allure.api.addTranslation('en', {
+    tab: {
+        behaviors: {
+            name: 'Behaviors'
+        }
+    },
+    widget: {
+        behaviors: {
+            name: 'Features by stories',
+            showAll: 'show all'
+        }
+    }
+});
+
+allure.api.addTranslation('ru', {
+    tab: {
+        behaviors: {
+            name: 'Функциональность'
+        }
+    },
+    widget: {
+        behaviors: {
+            name: 'Функциональность',
+            showAll: 'показать все'
+        }
+    }
+});
+
+allure.api.addTranslation('zh', {
+    tab: {
+        behaviors: {
+            name: '功能'
+        }
+    },
+    widget: {
+        behaviors: {
+            name: '特性场景',
+            showAll: '显示所有'
+        }
+    }
+});
+
+allure.api.addTranslation('de', {
+    tab: {
+        behaviors: {
+            name: 'Verhalten'
+        }
+    },
+    widget: {
+        behaviors: {
+            name: 'Features nach Stories',
+            showAll: 'Zeige alle'
+        }
+    }
+});
+
+allure.api.addTranslation('nl', {
+    tab: {
+        behaviors: {
+            name: 'Functionaliteit'
+        }
+    },
+    widget: {
+        behaviors: {
+            name: 'Features en story’s',
+            showAll: 'Toon alle'
+        }
+    }
+});
+
+allure.api.addTranslation('he', {
+    tab: {
+        behaviors: {
+            name: 'התנהגויות'
+        }
+    },
+    widget: {
+        behaviors: {
+            name: 'תכונות לפי סיפורי משתמש',
+            showAll: 'הצג הכול'
+        }
+    }
+});
+
+allure.api.addTranslation('br', {
+    tab: {
+        behaviors: {
+            name: 'Comportamentos'
+        }
+    },
+    widget: {
+        behaviors: {
+            name: 'Funcionalidades por história',
+            showAll: 'Mostrar tudo'
+        }
+    }
+});
+
+allure.api.addTranslation('ja', {
+    tab: {
+        behaviors: {
+            name: '振る舞い'
+        }
+    },
+    widget: {
+        behaviors: {
+            name: 'ストーリー別の機能',
+            showAll: '全て表示'
+        }
+    }
+});
+
+allure.api.addTranslation('es', {
+    tab: {
+        behaviors: {
+            name: 'Funcionalidades'
+        }
+    },
+    widget: {
+        behaviors: {
+            name: 'Funcionalidades por Historias de Usuario',
+            showAll: 'mostrar todo'
+        }
+    }
+});
+
+allure.api.addTranslation('kr', {
+    tab: {
+        behaviors: {
+            name: '동작'
+        }
+    },
+    widget: {
+        behaviors: {
+            name: '스토리별 기능',
+            showAll: '전체 보기'
+        }
+    }
+});
+
+allure.api.addTranslation('fr', {
+    tab: {
+        behaviors: {
+            name: 'Comportements'
+        }
+    },
+    widget: {
+        behaviors: {
+            name: 'Thèmes par histoires',
+            showAll: 'Montrer tout'
+        }
+    }
+});
+
+allure.api.addTranslation('pl', {
+    tab: {
+        behaviors: {
+            name: 'Zachowania'
+        }
+    },
+    widget: {
+        behaviors: {
+            name: 'Funkcje według historii',
+            showAll: 'pokaż wszystko'
+        }
+    }
+});
+
+allure.api.addTranslation('az', {
+    tab: {
+        behaviors: {
+            name: 'Davranışlar'
+        }
+    },
+    widget: {
+        behaviors: {
+            name: 'Hekayələr üzrə xüsusiyyətlər',
+            showAll: 'hamısını göstər'
+        }
+    }
+});
+
+allure.api.addTranslation('sv', {
+    tab: {
+        behaviors: {
+            name: 'Beteenden'
+        }
+    },
+    widget: {
+        behaviors: {
+            name: 'Funktioner efter user stories',
+            showAll: 'visa allt'
+        }
+    }
+});
+
+allure.api.addTranslation('isv', {
+    tab: {
+        behaviors: {
+            name: 'Funkcionalnost',
+        }
+    },
+    widget: {
+        behaviors: {
+            name: 'Funkcionalnost',
+            showAll: 'pokaži vsěčto',
+        }
+    }
+});
+
+allure.api.addTranslation('ka', {
+    tab: {
+        behaviors: {
+            name: 'ფუნქციონალი',
+        }
+    },
+    widget: {
+        behaviors: {
+            name: 'ფუნქციონალი',
+            showAll: 'ყველას ჩვენება',
+        }
+    }
+});
+
+allure.api.addTranslation('it', {
+    tab: {
+        behaviors: {
+            name: 'Comportamenti'
+        }
+    },
+    widget: {
+        behaviors: {
+            name: 'Funzionalità per storie',
+            showAll: 'Mostra tutto'
+        }
+    }
+});
+
+allure.api.addTab('behaviors', {
+    title: 'tab.behaviors.name', icon: 'fa fa-list',
+    route: 'behaviors(/)(:testGroup)(/)(:testResult)(/)(:testResultTab)(/)',
+    onEnter: (function (testGroup, testResult, testResultTab) {
+        return new allure.components.TreeLayout({
+            testGroup: testGroup,
+            testResult: testResult,
+            testResultTab: testResultTab,
+            tabName: 'tab.behaviors.name',
+            baseUrl: 'behaviors',
+            url: 'data/behaviors.json',
+            csvUrl: 'data/behaviors.csv'
+        });
+    })
+});
+
+allure.api.addWidget('widgets', 'behaviors', allure.components.WidgetStatusView.extend({
+    rowTag: 'a',
+    title: 'widget.behaviors.name',
+    baseUrl: 'behaviors',
+    showLinks: true
+}));

+ 152 - 0
report/plugin/packages/index.js

@@ -0,0 +1,152 @@
+'use strict';
+
+allure.api.addTranslation('en', {
+    tab: {
+        packages: {
+            name: 'Packages'
+        }
+    }
+});
+
+allure.api.addTranslation('ru', {
+    tab: {
+        packages: {
+            name: 'Пакеты'
+        }
+    }
+});
+
+allure.api.addTranslation('zh', {
+    tab: {
+        packages: {
+            name: '包'
+        }
+    }
+});
+
+allure.api.addTranslation('de', {
+    tab: {
+        packages: {
+            name: 'Pakete'
+        }
+    }
+});
+
+allure.api.addTranslation('nl', {
+    tab: {
+        packages: {
+            name: 'Packages'
+        }
+    }
+});
+
+allure.api.addTranslation('he', {
+    tab: {
+        packages: {
+            name: 'חבילות'
+        }
+    }
+});
+
+allure.api.addTranslation('br', {
+    tab: {
+        packages: {
+            name: 'Pacotes'
+        }
+    }
+});
+
+allure.api.addTranslation('ja', {
+    tab: {
+        packages: {
+            name: 'パッケージ'
+        }
+    }
+});
+
+allure.api.addTranslation('es', {
+    tab: {
+        packages: {
+            name: 'Paquetes'
+        }
+    }
+});
+
+allure.api.addTranslation('kr', {
+    tab: {
+        packages: {
+            name: '패키지'
+        }
+    }
+});
+
+allure.api.addTranslation('fr', {
+    tab: {
+        packages: {
+            name: 'Paquets'
+        }
+    }
+});
+
+allure.api.addTranslation('pl', {
+    tab: {
+        packages: {
+            name: 'Pakiety'
+        }
+    }
+});
+
+allure.api.addTranslation('az', {
+    tab: {
+        packages: {
+            name: 'Paketlər'
+        }
+    }
+});
+
+allure.api.addTranslation('sv', {
+    tab: {
+        packages: {
+            name: 'Paket'
+        }
+    }
+});
+
+allure.api.addTranslation('isv', {
+    tab: {
+        packages: {
+            name: 'Pakety'
+        }
+    }
+});
+
+allure.api.addTranslation('ka', {
+    tab: {
+        packages: {
+            name: 'პაკეტები'
+        }
+    }
+});
+
+allure.api.addTranslation('it', {
+    tab: {
+        packages: {
+            name: 'Pacchetti'
+        }
+    }
+});
+
+allure.api.addTab('packages', {
+    title: 'tab.packages.name', icon: 'fa fa-align-left',
+    route: 'packages(/)(:testGroup)(/)(:testResult)(/)(:testResultTab)(/)',
+    onEnter: (function (testGroup, testResult, testResultTab) {
+        return new allure.components.TreeLayout({
+            testGroup: testGroup,
+            testResult: testResult,
+            testResultTab: testResultTab,
+            tabName: 'tab.packages.name',
+            baseUrl: 'packages',
+            url: 'data/packages.json'
+        });
+    })
+});

+ 200 - 0
report/plugin/screen-diff/index.js

@@ -0,0 +1,200 @@
+(function () {
+    var settings = allure.getPluginSettings('screen-diff', { diffType: 'diff' });
+
+    function renderImage(src) {
+        return (
+            '<div class="screen-diff__container">' +
+            '<img class="screen-diff__image" src="' +
+            src +
+            '">' +
+            '</div>'
+        );
+    }
+
+    function findImage(data, name) {
+        if (data.testStage && data.testStage.attachments) {
+            var matchedImage = data.testStage.attachments.filter(function (attachment) {
+                return attachment.name === name;
+            })[0];
+            if (matchedImage) {
+                return 'data/attachments/' + matchedImage.source;
+            }
+        }
+        return null;
+    }
+
+    function renderDiffContent(type, diffImage, actualImage, expectedImage) {
+        if (type === 'diff') {
+            if (diffImage) {
+                return renderImage(diffImage);
+            }
+        }
+        if (type === 'overlay' && expectedImage) {
+            return (
+                '<div class="screen-diff__overlay screen-diff__container">' +
+                '<img class="screen-diff__image" src="' +
+                expectedImage +
+                '">' +
+                '<div class="screen-diff__image-over">' +
+                '<img class="screen-diff__image" src="' +
+                actualImage +
+                '">' +
+                '</div>' +
+                '</div>'
+            );
+        }
+        if (actualImage) {
+            return renderImage(actualImage);
+        }
+        return 'No diff data provided';
+    }
+
+    var TestResultView = Backbone.Marionette.View.extend({
+        regions: {
+            subView: '.screen-diff-view',
+        },
+        template: function () {
+            return '<div class="screen-diff-view"></div>';
+        },
+        onRender: function () {
+            var data = this.model.toJSON();
+            var testType = data.labels.filter(function (label) {
+                return label.name === 'testType';
+            })[0];
+            var diffImage = findImage(data, 'diff');
+            var actualImage = findImage(data, 'actual');
+            var expectedImage = findImage(data, 'expected');
+            if (!testType || testType.value !== 'screenshotDiff') {
+                return;
+            }
+            this.showChildView(
+                'subView',
+                new ScreenDiffView({
+                    diffImage: diffImage,
+                    actualImage: actualImage,
+                    expectedImage: expectedImage,
+                }),
+            );
+        },
+    });
+    var ErrorView = Backbone.Marionette.View.extend({
+        templateContext: function () {
+            return this.options;
+        },
+        template: function (data) {
+            return '<pre class="screen-diff-error">' + data.error + '</pre>';
+        },
+    });
+    var AttachmentView = Backbone.Marionette.View.extend({
+        regions: {
+            subView: '.screen-diff-view',
+        },
+        template: function () {
+            return '<div class="screen-diff-view"></div>';
+        },
+        onRender: function () {
+            jQuery
+                .getJSON(this.options.sourceUrl)
+                .then(this.renderScreenDiffView.bind(this), this.renderErrorView.bind(this));
+        },
+        renderErrorView: function (error) {
+            console.log(error);
+            this.showChildView(
+                'subView',
+                new ErrorView({
+                    error: error.statusText,
+                }),
+            );
+        },
+        renderScreenDiffView: function (data) {
+            this.showChildView(
+                'subView',
+                new ScreenDiffView({
+                    diffImage: data.diff,
+                    actualImage: data.actual,
+                    expectedImage: data.expected,
+                }),
+            );
+        },
+    });
+
+    var ScreenDiffView = Backbone.Marionette.View.extend({
+        className: 'pane__section',
+        events: function () {
+            return {
+                ['click [name="screen-diff-type-' + this.cid + '"]']: 'onDiffTypeChange',
+                'mousemove .screen-diff__overlay': 'onOverlayMove',
+            };
+        },
+        initialize: function (options) {
+            this.diffImage = options.diffImage;
+            this.actualImage = options.actualImage;
+            this.expectedImage = options.expectedImage;
+            this.radioName = 'screen-diff-type-' + this.cid;
+        },
+        templateContext: function () {
+            return {
+                diffType: settings.get('diffType'),
+                diffImage: this.diffImage,
+                actualImage: this.actualImage,
+                expectedImage: this.expectedImage,
+                radioName: this.radioName,
+            };
+        },
+        template: function (data) {
+            if (!data.diffImage && !data.actualImage && !data.expectedImage) {
+                return '';
+            }
+
+            return (
+                '<h3 class="pane__section-title">Screen Diff</h3>' +
+                '<div class="screen-diff__content">' +
+                '<div class="screen-diff__switchers">' +
+                '<label><input type="radio" name="' +
+                data.radioName +
+                '" value="diff"> Show diff</label>' +
+                '<label><input type="radio" name="' +
+                data.radioName +
+                '" value="overlay"> Show overlay</label>' +
+                '</div>' +
+                renderDiffContent(
+                    data.diffType,
+                    data.diffImage,
+                    data.actualImage,
+                    data.expectedImage,
+                ) +
+                '</div>'
+            );
+        },
+        adjustImageSize: function (event) {
+            var overImage = this.$(event.target);
+            overImage.width(overImage.width());
+        },
+        onRender: function () {
+            const diffType = settings.get('diffType');
+            this.$('[name="' + this.radioName + '"][value="' + diffType + '"]').prop(
+                'checked',
+                true,
+            );
+            if (diffType === 'overlay') {
+                this.$('.screen-diff__image-over img').on('load', this.adjustImageSize.bind(this));
+            }
+        },
+        onOverlayMove: function (event) {
+            var pageX = event.pageX;
+            var containerScroll = this.$('.screen-diff__container').scrollLeft();
+            var elementX = event.currentTarget.getBoundingClientRect().left;
+            var delta = pageX - elementX + containerScroll;
+            this.$('.screen-diff__image-over').width(delta);
+        },
+        onDiffTypeChange: function (event) {
+            settings.save('diffType', event.target.value);
+            this.render();
+        },
+    });
+    allure.api.addTestResultBlock(TestResultView, { position: 'before' });
+    allure.api.addAttachmentViewer('application/vnd.allure.image.diff', {
+        View: AttachmentView,
+        icon: 'fa fa-exchange',
+    });
+})();

+ 30 - 0
report/plugin/screen-diff/styles.css

@@ -0,0 +1,30 @@
+.screen-diff__switchers {
+  margin-bottom: 1em;
+}
+
+.screen-diff__switchers label + label {
+  margin-left: 1em;
+}
+
+.screen-diff__overlay {
+  position: relative;
+  cursor: col-resize;
+}
+
+.screen-diff__container {
+  overflow-x: auto;
+}
+
+.screen-diff__image-over {
+  top: 0;
+  left: 0;
+  bottom: 0;
+  background: #fff;
+  position: absolute;
+  overflow: hidden;
+  box-shadow: 2px 0 1px -1px #aaa;
+}
+
+.screen-diff-error {
+  color: #fd5a3e;
+}

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 3 - 0
report/styles.css


+ 1 - 0
report/widgets/behaviors.json

@@ -0,0 +1 @@
+{"total":1,"items":[{"uid":"da57807998991c26b8caf4dd6ec98a5a","name":"读书屋项目","statistic":{"failed":1,"broken":0,"skipped":0,"passed":2,"unknown":0,"total":3}}]}

+ 1 - 0
report/widgets/categories-trend.json

@@ -0,0 +1 @@
+[{"data":{"Product defects":1,"Test defects":1}}]

+ 1 - 0
report/widgets/categories.json

@@ -0,0 +1 @@
+{"total":2,"items":[{"uid":"8fb3a91ba5aaf9de24cc8a92edc82b5d","name":"Product defects","statistic":{"failed":1,"broken":0,"skipped":0,"passed":0,"unknown":0,"total":1}},{"uid":"bdbf199525818fae7a8651db9eafe741","name":"Test defects","statistic":{"failed":0,"broken":1,"skipped":0,"passed":0,"unknown":0,"total":1}}]}

+ 1 - 0
report/widgets/duration-trend.json

@@ -0,0 +1 @@
+[{"data":{"duration":60606}}]

+ 1 - 0
report/widgets/duration.json

@@ -0,0 +1 @@
+[{"uid":"b5b354cd32adbc50","name":"登陆失败用例,用户名为155742139,密码123457","time":{"start":1752642885007,"stop":1752642886505,"duration":1498},"status":"passed","severity":"blocker"},{"uid":"2676f1432c30332d","name":"登陆成功用例,用户名为15574113900,密码123456","time":{"start":1752642861261,"stop":1752642864349,"duration":3088},"status":"passed","severity":"normal"},{"uid":"7e597899afd88891","name":"登陆失败用例,用户名为15574413902,密码123458","time":{"start":1752642890585,"stop":1752642892820,"duration":2235},"status":"failed","severity":"blocker"},{"uid":"30fcaadeb0b2b005","name":"登陆失败用例,用户名为15574213900,密码123456","time":{"start":1752642868620,"stop":1752642880516,"duration":11896},"status":"broken","severity":"blocker"},{"uid":"b5b0ec12a20631c7","name":"登陆成功用例,用户名为15574113907,密码123456","time":{"start":1752642854232,"stop":1752642857113,"duration":2881},"status":"passed","severity":"normal"},{"uid":"93232cb4c6c9e5e2","name":"登陆-搜索书籍进行评论用例","time":{"start":1752642841897,"stop":1752642850000,"duration":8103},"status":"passed","severity":"normal"},{"uid":"1fb90c11fcde3133","name":"登陆-搜索书籍用例","time":{"start":1752642896993,"stop":1752642902503,"duration":5510},"status":"passed","severity":"normal"}]

+ 1 - 0
report/widgets/environment.json

@@ -0,0 +1 @@
+[]

+ 1 - 0
report/widgets/executors.json

@@ -0,0 +1 @@
+[]

+ 1 - 0
report/widgets/history-trend.json

@@ -0,0 +1 @@
+[{"data":{"failed":1,"broken":1,"skipped":0,"passed":5,"unknown":0,"total":7}}]

+ 1 - 0
report/widgets/launch.json

@@ -0,0 +1 @@
+[]

+ 1 - 0
report/widgets/retry-trend.json

@@ -0,0 +1 @@
+[{"data":{"run":7,"retry":0}}]

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно