Commit 32d8d7f1 authored by 王超's avatar 王超

框架优化

parents
name: Playwright Tests
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
.svn/
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
<?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.9" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
Private property of Exodus: 1044@wangchaodeMBP
jetbrains.exodus.io.LockingManager.lock(LockingManager.kt:88)
jetbrains.exodus.io.LockingManager.lock(LockingManager.kt:39)
jetbrains.exodus.io.FileDataWriter.lock(FileDataWriter.kt:70)
jetbrains.exodus.log.Log.tryLock(Log.kt:804)
jetbrains.exodus.log.Log.<init>(Log.kt:117)
jetbrains.exodus.env.Environments.newLogInstance(Environments.kt:117)
jetbrains.exodus.env.Environments.newLogInstance(Environments.kt:81)
jetbrains.exodus.env.Environments.newLogInstance(Environments.kt:77)
jetbrains.exodus.env.Environments$newInstance$4.invoke(Environments.kt:46)
jetbrains.exodus.env.Environments$newInstance$4.invoke(Environments.kt:46)
jetbrains.exodus.env.Environments.prepare(Environments.kt:120)
jetbrains.exodus.env.Environments.newInstance(Environments.kt:46)
kotlinx.dnq.store.container.EntityStoreHelperKt.createTransientEntityStore(EntityStoreHelper.kt:40)
kotlinx.dnq.store.container.EntityStoreHelperKt.createTransientEntityStore(EntityStoreHelper.kt:31)
kotlinx.dnq.store.container.EntityStoreHelperKt.createTransientEntityStore$default(EntityStoreHelper.kt:30)
com.github.copilot.chat.session.persistence.xodus.XdChatSessionPersistenceService.initStore(XdChatSessionPersistenceService.kt:115)
com.github.copilot.chat.session.persistence.xodus.XdChatSessionPersistenceService.<init>(XdChatSessionPersistenceService.kt:22)
com.github.copilot.chat.session.persistence.xodus.XdChatSessionPersistenceService.<init>(XdChatSessionPersistenceService.kt:15)
com.github.copilot.chat.session.persistence.ChatSessionPersistenceServiceKt.ChatSessionPersistenceService(ChatSessionPersistenceService.kt:43)
com.github.copilot.chat.session.persistence.ChatSessionPersistenceServiceKt.chatSessionsPersistenceService(ChatSessionPersistenceService.kt:53)
com.github.copilot.chat.session.ChatSessionManager.<init>(ChatSessionManager.kt:45)
com.github.copilot.chat.session.ChatSessionManager.<init>(ChatSessionManager.kt:25)
com.github.copilot.chat.window.CopilotChatToolWindow.onCopilotReady(CopilotChatToolWindow.kt:133)
com.github.copilot.chat.window.CopilotChatToolWindow.access$onCopilotReady(CopilotChatToolWindow.kt:40)
com.github.copilot.chat.window.CopilotChatToolWindow$initCopilotStatusListener$1.invoke(CopilotChatToolWindow.kt:118)
com.github.copilot.chat.window.CopilotChatToolWindow$initCopilotStatusListener$1.invoke(CopilotChatToolWindow.kt:115)
com.github.copilot.status.CopilotAuthStatusKt.subscribeToCopilotAuthStatus(CopilotAuthStatus.kt:27)
com.github.copilot.chat.window.CopilotChatToolWindow.initCopilotStatusListener(CopilotChatToolWindow.kt:115)
com.github.copilot.chat.window.CopilotChatToolWindow.<init>(CopilotChatToolWindow.kt:59)
com.github.copilot.chat.window.CopilotChatToolWindow.<init>(CopilotChatToolWindow.kt:40)
com.github.copilot.chat.window.CopilotChatToolWindowFactory.init(CopilotChatToolWindowFactory.kt:18)
com.intellij.openapi.wm.impl.ToolWindowManagerImpl.registerToolWindow$intellij_platform_ide_impl(ToolWindowManagerImpl.kt:1084)
com.intellij.toolWindow.ToolWindowSetInitializerKt.registerToolWindows(ToolWindowSetInitializer.kt:181)
com.intellij.toolWindow.ToolWindowSetInitializerKt.access$registerToolWindows(ToolWindowSetInitializer.kt:1)
com.intellij.toolWindow.ToolWindowSetInitializer$createAndLayoutToolWindows$entries$1$1.invoke(ToolWindowSetInitializer.kt:129)
com.intellij.toolWindow.ToolWindowSetInitializer$createAndLayoutToolWindows$entries$1$1.invoke(ToolWindowSetInitializer.kt:121)
com.intellij.openapi.progress.CoroutinesKt.blockingContext(coroutines.kt:248)
com.intellij.openapi.progress.CoroutinesKt.blockingContext(coroutines.kt:199)
com.intellij.toolWindow.ToolWindowSetInitializer$createAndLayoutToolWindows$entries$1.invokeSuspend(ToolWindowSetInitializer.kt:121)
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
com.intellij.openapi.application.TransactionGuardImpl$1.run(TransactionGuardImpl.java:193)
com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:861)
com.intellij.openapi.application.impl.ApplicationImpl$4.run(ApplicationImpl.java:478)
com.intellij.openapi.application.impl.FlushQueue.doRun(FlushQueue.java:79)
com.intellij.openapi.application.impl.FlushQueue.runNextEvent(FlushQueue.java:121)
com.intellij.openapi.application.impl.FlushQueue.flushNow(FlushQueue.java:41)
java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318)
java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:792)
java.desktop/java.awt.EventQueue$3.run(EventQueue.java:739)
java.desktop/java.awt.EventQueue$3.run(EventQueue.java:733)
java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:761)
com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.kt:690)
com.intellij.ide.IdeEventQueue._dispatchEvent$lambda$10(IdeEventQueue.kt:593)
com.intellij.openapi.application.impl.ApplicationImpl.runWithoutImplicitRead(ApplicationImpl.java:1485)
com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.kt:593)
com.intellij.ide.IdeEventQueue.access$_dispatchEvent(IdeEventQueue.kt:67)
com.intellij.ide.IdeEventQueue$dispatchEvent$processEventRunnable$1$1$1.compute(IdeEventQueue.kt:369)
com.intellij.ide.IdeEventQueue$dispatchEvent$processEventRunnable$1$1$1.compute(IdeEventQueue.kt:368)
com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:787)
com.intellij.ide.IdeEventQueue$dispatchEvent$processEventRunnable$1$1.invoke(IdeEventQueue.kt:368)
com.intellij.ide.IdeEventQueue$dispatchEvent$processEventRunnable$1$1.invoke(IdeEventQueue.kt:363)
com.intellij.ide.IdeEventQueueKt.performActivity$lambda$1(IdeEventQueue.kt:997)
com.intellij.openapi.application.TransactionGuardImpl.performActivity(TransactionGuardImpl.java:105)
com.intellij.ide.IdeEventQueueKt.performActivity(IdeEventQueue.kt:997)
com.intellij.ide.IdeEventQueue.dispatchEvent$lambda$7(IdeEventQueue.kt:363)
com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:861)
com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.kt:405)
java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:207)
java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:128)
java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:117)
java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:113)
java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:105)
java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:92)
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CsvFileAttributes">
<option name="attributeMap">
<map>
<entry key="\TestReport\AllureReport\data\suites.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
</map>
</option>
</component>
</project>
\ No newline at end of file
<component name="ProjectDictionaryState">
<dictionary name="Administrator" />
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" native2AsciiForPropertiesFiles="true" defaultCharsetForPropertiesFiles="UTF-8">
<file url="file://$PROJECT_DIR$" charset="UTF-8" />
<file url="PROJECT" charset="UTF-8" />
</component>
</project>
\ No newline at end of file
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ourVersions">
<value>
<list size="2">
<item index="0" class="java.lang.String" itemvalue="3.9" />
<item index="1" class="java.lang.String" itemvalue="3.10" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>
\ No newline at end of file
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.10 (CMS2.0_UI_AUTO_TEST)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9" project-jdk-type="Python SDK" />
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/CMS2.0_UI_AUTO_TEST.iml" filepath="$PROJECT_DIR$/.idea/CMS2.0_UI_AUTO_TEST.iml" />
</modules>
</component>
</project>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
<changelist name="Uncommitted_changes_before_Update_at_2024_08_15_10_22_[Changes]" date="1723688580646" recycled="false" toDelete="true">
<option name="PATH" value="$PROJECT_DIR$/.idea/shelf/Uncommitted_changes_before_Update_at_2024_08_15_10_22_[Changes]/shelved.patch" />
<option name="DESCRIPTION" value="Uncommitted changes before Update at 2024/08/15 10:22 [Changes]" />
</changelist>
\ No newline at end of file
<changelist name="Uncommitted_changes_before_Update_at_2024_08_15_10_22_[Changes]1" date="1723688678095" recycled="true" deleted="true">
<option name="PATH" value="$PROJECT_DIR$/.idea/shelf/Uncommitted_changes_before_Update_at_2024_08_15_10_22_[Changes]1/shelved.patch" />
<option name="DESCRIPTION" value="Uncommitted changes before Update at 2024/08/15 10:22 [Changes]" />
</changelist>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="0efa5718-fb1e-4917-aed6-7a6f1e1781c9" name="Changes" comment="1.修改工程ID为自动化获取&#10;2.BasePage.py中增加对鼠标拖动的支持&#10;3.PageManagementPage.py中增加页面对象公共属性操作的封装&#10;4.增加图像比对断言的方法&#10;5.更新README文件">
<change afterPath="$PROJECT_DIR$/.idea/dictionaries/Administrator.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/encodings.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/encodings.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/BasePage/BasePage.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/BasePage/__init__.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/BuildInLibrary/BuildInLibrary.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/BuildInLibrary/__init__.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Common/AllurePretty.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Common/ImageComparison.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Common/ImageRecognitionClick.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Common/Rectangle_actual.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Common/Rectangle_expected.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Common/Rectangle_expected2.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Common/ScreenCapture.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Common/__init__.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Common/marked_difference.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Config/Config.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Config/__init__.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Config/global_config.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ImageComparison/actual_img/矩形_修改xy属性_实际图.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ImageComparison/actual_img/矩形_修改旋转角度_实际图.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ImageComparison/actual_img/矩形_创建矩形_实际图.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ImageComparison/expected_img/矩形_修改xy属性_预期图.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ImageComparison/expected_img/矩形_修改旋转角度_预期图.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ImageComparison/expected_img/矩形_创建矩形_预期图.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ImageRecognition/screenshot_img/1723174060屏幕截图.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ImageRecognition/screenshot_img/1723174351屏幕截图.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ImageRecognition/screenshot_img/1723174862屏幕截图.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ImageRecognition/screenshot_img/1723174906屏幕截图.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ImageRecognition/screenshot_img/1723175414屏幕截图.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ImageRecognition/screenshot_img/1723175654屏幕截图.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ImageRecognition/screenshot_img/1723175953屏幕截图.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ImageRecognition/template_img/Snipaste_2024-08-09_11-57-40.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240814_1723614548.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240814_1723623890.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240814_1723624325.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240814_1723624678.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240814_1723624740.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240814_1723624955.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240814_1723631766.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723688158.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723688360.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723702220.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723702347.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723702369.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723702394.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723702861.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723702888.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723703031.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723703162.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723703262.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723703325.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723703355.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723703845.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723704240.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723704376.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723704547.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723705066.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723705102.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723705129.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723705520.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723705845.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723705997.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723706222.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723707091.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723707164.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723707952.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723708231.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723708542.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723708785.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723709201.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723709832.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723709967.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723710056.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723710151.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723710274.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723710362.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723710392.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723711082.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723711240.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723711454.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723712064.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723712801.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723712846.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723712940.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723713292.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723713501.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723713619.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723714374.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723714475.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723715267.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240815_1723715399.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723773226.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723773449.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723773544.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723773688.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723773803.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723774076.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723774319.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723774435.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723774762.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723774900.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723775206.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723775237.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723775333.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723775477.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723775680.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723775779.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723775828.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723775887.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723775941.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723776016.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723776082.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723776159.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723776264.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723776344.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723777771.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723778155.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723778217.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723778277.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723778352.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723778436.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723778534.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723778550.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723778683.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723778813.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723778960.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723779372.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723779472.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723779862.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723779996.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723780420.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723786592.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723786697.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723787003.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723787292.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723787553.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723788287.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723789062.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723789168.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723789350.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723792598.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723792696.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723792748.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723792835.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723792941.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723793070.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723793104.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723793179.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723793247.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723793311.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723793396.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723797044.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723797114.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723802793.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240816_1723803088.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240819_1724036363.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240819_1724038133.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240819_1724038230.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240819_1724038543.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240819_1724038836.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/console_log_20240819_1724038988.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240814_1723623961.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723702348.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723702369.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723702395.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723702862.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723703053.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723703190.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723703284.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723703326.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723703356.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723703846.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723704241.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723704376.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723704548.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723705067.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723705103.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723705104.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723705106.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723705108.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723705190.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723705234.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723705260.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723705558.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723705880.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723706032.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723706222.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723707097.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723707181.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723707966.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723708245.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723708556.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723708800.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723710063.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723710158.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723711096.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723711255.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723712815.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723712860.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723712954.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723713306.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723713515.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723713633.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240815_1723715406.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723773260.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723773463.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723773568.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723773707.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723773834.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723774115.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723774345.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723774457.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723774780.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723774975.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723775253.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723775358.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723775496.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723775696.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723775802.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723778173.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723778233.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723778294.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723778385.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723778452.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723779390.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723792613.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723792711.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723792762.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723792849.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723792956.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723793338.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723793349.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723793422.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723793433.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723797061.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240816_1723802834.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240819_1724038147.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240819_1724038557.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240819_1724038851.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240819_1724038860.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Logs/failure_log_20240819_1724038869.log" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Pages/HistoryLibraryPage/HistoryLibraryManagement.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Pages/PageManagementPage/PageManagementPage.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Pages/ProjectManagementPage/ProjectManagementPage.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Pages/VariableManagementPage/VariableManagementPage.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Pages/__init__.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/1Test1_HistoryLibraryManagement.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/1Test1_ProjectManagement.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/1Test1_VariableManagement.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/Test_PageManagement.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/__init__.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/01af1d111d5150a596fc875cc4bc3a0c.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/0375bab6903d101204bc9257ffce4e63.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/0716ce5992defa52924db2e0fdf43eff.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/0b759c73a44b086af9f10f326e4cc163.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/0c8c8cf088bcee9637851b82d61fd9b7.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/16d53c6abc736c701e7284b892c37ab3.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/1aeef9bcda82e05e75ba034e76073ae8.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/1bd2bc59895b10ae00953fe4e72100ee.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/1fa12811e5aa033781973ffa0b05d71a.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/225a872d97fe38729e263936bd03d890.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/25953b266e7565c52d5f024f6552f514.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/27435ff73f2a17240517659127ec8f91.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/2afb9d0ac91452255558da311f6dfb41.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/36dd25c4803b7ad001ff37dca093a55e.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/36f294031fa2d54e83946a34804189f8.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/3709a3a35fb97e9bd511759039dc3615.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/3efabeb4b1610286a303c82691d51677.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/3fb4745df714ff9bbd59213a9c772880.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/43a6e048500ef387e7d76ade2b0eb6df.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/49335d573aee530052b317ecce260b58.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/4ceee97597f51939ed1aac65fbda114a.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/5368d459b154006cf508bc6119dc3b1f.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/560404b4192f72e3d0cbbe041d0a043d.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/5802996275027fcba46c1a7832dfef8e.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/5dfc6d16d55d10011b87f140549df697.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/64556c7013aceeae30000ad943746b0b.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/6d7090f06805e7412cfdf93f0eb92fe5.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/6e7df8968f1862517c4856b7eab9cfb7.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/70af79a874ac79303a0dee305993e438.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/7b0cae65806cae96c3e491f43b259e3e.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/80a3c9dd29c085560236d41865059c24.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/8175d84113f03cc52378e6be95aff740.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/8278b2dfea40543a48e7ed110408dbf5.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/8352675d5cf5581cac06ffb8ec184132.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/8616b836f0928974240333563e5e7204.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/8decb00a8056414114f4a164fb92626e.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/8f20e68d854e56ab16e622c6fb7baf9d.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/8f72a1919f7edbeb8be0f62d38629636.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/8fc8d2fd99c8b7c7185c6047d897fea3.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/900272603b184a5fe499e3f4bfda25e1.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/9337dca144dc08cb68914e9374bf3392.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/95c3eb5e7dea808de6d77ec337d86cae.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/97b781c6759fabeeb43408b99000f3ce.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/9b1a29479511cdaf86a0bc590cdc1026.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/9cb15fe58a3fc60cdd9de96790e502bf.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/9d42ed72c0583687762d3f3e472a03bb.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/9d7f47a0c8a4ef65cb6d15a6769eb939.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/9dcd3f0abb21aa24cc914e9f6dce0eab.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/a12ea58cb8ad7def0f13f92b2ca81d8f.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/a59fd7cc35c6a8f1f672fc5681730d13.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/a686f411a73239b0a71c39ab38f86b30.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/ab5995bd0692a4fb58e78ed2827c8219.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/afb1486677575c23b737e4bfc97b254e.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/b03f27d294592253c111234764cbe417.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/b606d2f3787e88a7aea1dcb5f803331b.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/b61beb9a2864983f6e6deb4a0caefabc.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/b698c6cdd5bfd9d483909b7a7ad7f334.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/bd7ed3535ae4e18a2a8cd7fee712df0d.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/c58329b623c94e1d2a5d748436f7026e.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/c71e866176d50c05763034a6182642ce.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/c83fdae1e13689862adc9bcdbf3268ac.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/ceefdccaca256cc0a70565ca28a6a346.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/d58175e8c944bf0936c57e2805bf6d80.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/e1a32e41e260ace36273ebf17e7cc9e9.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/e537cc28f1cd3d7558e28f5180504faf.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/e5f63c0c0a7813506f186ab71bc0286f.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/ead488fa5d96420f742c2b0c08bc49e7.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/f5c707062ebced9ec16ad9780aa8b21f.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/f846b8cde3a4bc39d587426ebf9427d6.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestCases/videos/ffe1d6fd53d6bd10b60bac7bcc0ea49a.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/HistoryLibraryManagementData/TestHistoryLibraryBackup.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/HistoryLibraryManagementData/TestHistoryLibraryClear.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/PageManagementData/TestPageComponentRectangleData.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/PageManagementData/TestPageComponentRectangle_Set_X_Axis_Data.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/PageManagementData/TestPageComponentTrendCurve_Add_Line_Group_Data.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/PageManagementData/TestPageExportData.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/PageManagementData/TestPageImportData.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/ProjectManagementData/TestBatchDeleteProjectData.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/ProjectManagementData/TestCreateProjectData.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/ProjectManagementData/TestCreateProjectFolderData.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/ProjectManagementData/TestDataBackupData.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/ProjectManagementData/TestDataClearData.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/ProjectManagementData/TestDataRecoveryData.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/ProjectManagementData/TestEditeProject_MoveTo.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/ProjectManagementData/TestEditeProject_Rename.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/ProjectManagementData/TestImportLocalProjectData.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/ProjectManagementData/TestSearchProjectData.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/VariableManagementData/TestBatchProcessVariable.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/VariableManagementData/TestGlobalExportVariable.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/VariableManagementData/TestGlobalImportVariable.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/VariableManagementData/TestIOVariableExport.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/VariableManagementData/TestIOVariableImport.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/VariableManagementData/TestInternalVariableExport.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/VariableManagementData/TestInternalVariableImport.yaml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestDatas/__init__.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestFiles/test.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/app.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/data/attachments/55647d8de50a7ae5.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/data/attachments/72c61bba529a8643.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/data/attachments/cbdbb10482bb8e5e.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/data/attachments/df920b49494369f2.txt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/data/behaviors.csv" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/data/behaviors.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/data/categories.csv" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/data/categories.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/data/packages.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/data/suites.csv" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/data/suites.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/data/test-cases/1265aa8f32ae8ff1.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/data/test-cases/789ff72bc9452e.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/data/test-cases/f6b962abb1bdb6ec.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/data/timeline.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/export/influxDbData.txt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/export/mail.html" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/export/prometheusData.txt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/favicon.ico" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/history/categories-trend.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/history/duration-trend.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/history/history-trend.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/history/history.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/history/retry-trend.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/index.html" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/plugins/behaviors/index.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/plugins/custom-logo/custom-logo.svg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/plugins/custom-logo/styles.css" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/plugins/custom-logo/syclogo.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/plugins/packages/index.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/plugins/screen-diff/index.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/plugins/screen-diff/styles.css" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/styles.css" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/widgets/behaviors.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/widgets/categories-trend.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/widgets/categories.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/widgets/duration-trend.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/widgets/duration.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/widgets/environment.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/widgets/executors.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/widgets/history-trend.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/widgets/launch.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/widgets/retry-trend.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/widgets/severity.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/widgets/status-chart.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/widgets/suites.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureReport/widgets/summary.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureResult/335839aa-f7b3-46a3-8614-ad0454a0abb7-result.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureResult/349a35bb-1e6e-4d6d-b021-9f4926267386-container.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureResult/3ab627a3-21a1-48a5-86cb-fb030fd89071-container.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureResult/4ed51f43-c658-426b-a551-5010c0b67215-container.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureResult/5bce3104-4718-4594-9dc3-b50bd7dafaeb-result.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureResult/9bf887dd-91a6-469e-8a91-8f5429681ed6-attachment.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureResult/9d0be11a-9991-4a3d-9066-565a697b5b32-container.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureResult/9f16340a-1294-4ef2-808d-31165531a821-container.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureResult/a77822cb-1c8d-461c-94a8-52129db58e9d-container.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureResult/abe6fc01-6b49-42ca-801a-2f12c9068110-result.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureResult/b433c2a8-ee37-46a9-9a88-43179313e941-container.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureResult/c3854da4-b402-4651-a085-a3c617bd6752-attachment.txt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureResult/d4120f2d-b3b9-4977-bb68-1df6c9c5d5c2-attachment.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureResult/e1b42930-0a75-4ec4-bc45-18ce26f31096-attachment.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/AllureResult/f1aecf37-9348-42cd-8cca-b7ceb3efceb6-container.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/__init__.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/categories.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TestReport/environment.properties" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Utils/GenerateUUID.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Utils/GetObjectPath.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Utils/HTTPClient.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Utils/ReadYaml.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Utils/VisualRegression.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Utils/__init__.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Utils/logger_util.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/conftest.py" beforeDir="false" afterPath="$PROJECT_DIR$/conftest.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/log_config.yml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/pytest.ini" beforeDir="false" afterPath="$PROJECT_DIR$/pytest.ini" afterDir="false" />
<change beforePath="$PROJECT_DIR$/runner.py" beforeDir="false" afterPath="$PROJECT_DIR$/runner.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/videos/00e02e99f7ac797651a4d340c50e1013.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/0267ef22edbba62a96f69cb7c80eae2b.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/0699716fe622044b3e5d0e79524c278b.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/06d936249f4898ac5fa35ecccdf356e7.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/08650f1bbe0fe901f3460ba031b1cbc3.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/0872e89ce2d4fa478b570566b42429d0.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/09d7f3ec04fd1e456d77a15ff434955d.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/09ffe32f194628c0921e17c6aac7877b.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/0a7227637f13282ded299c9acb6fea42.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/0a8bcef45d962713662ac5615b15f6b8.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/0ae24a6b9dd8bd66f5cdab3f941342bc.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/0b326a6ae0a591bd58ea28189864060c.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/0fed94582607e49b2cf8449654efbfd9.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/13252ab77d096cd46d435cf19e291e1c.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/14e7f9ef1f748663955a1300dec5146c.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/1825489030756d572f064ddf35f1a647.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/184e066cbbfb612dd264cac7f68f7648.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/1c43b2bb54db6e37d7ea3f0a55f1eb48.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/1c877607d65f3ce88fcffd77ed0c5add.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/1dec3a51f32c5fa465af46aa39470d09.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/1e386a81a4e965fbde22c260445ac84a.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/1e5d14fe8c32077f26c88053c42cdf91.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/1f237968984c0d936974715ba449d9be.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/20091010146a56f2947e687661c5b1cf.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/2095f8c9d43faea11ac0fdce9ef77cfa.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/223c52ac85fe7ed88f9f4a6b2411df7c.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/252a6e7be784707a2c68b5c33754323d.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/2739d41647c22680e159f1a4ef2574de.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/29485090782bda89f38d3b1dca5fe11e.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/2bfdf831e5b063ddc471a55eb5325136.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/2d2120e85aa7bf64ec85657800d7051e.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/338e60f9496ef4774fb0b73dc18ee91e.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/34bf96679f07fb9336672a2c292d1ce9.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/377406056a87d0644c90e3c0b34fbbb8.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/3799bd46250aa2bf974056e23d788154.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/37c1b05de253823081317b0f062b3310.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/37e13d62c317a87116c18c2950f277b5.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/3b253bf54953f1575065efab8f86a963.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/3d5276849173f575e36c3e187df9041d.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/3daa1e58cf5e019e3e36555ef2b2e0a0.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/3ec4239febae2451513b341043fe74c6.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/42deaebf55697fc383231ad4df662b38.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/45f4420d46a076a7688b2404b4e8471a.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/47be28fdb74c4ace1ecba9c9bb9dc19a.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/4d8f6a799471245ec88643b8f6590dd1.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/4da5e07608cf91f9bc78bbf772b91903.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/4dd0dddeda819dd05b00f1a4b8dbb037.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/4ed002ca7831a94cbf612f739d38347d.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/4fb09ae9170d4eb5c3a7bff0899c9131.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/501af3609ede3d0bc7c3cd46079ae43e.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/50655cf43e931b500d290e27a751741b.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/541db520e989f2339949fd1c537e4097.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/542b6273152d66c2886df7665a3b99aa.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/56a806c060f8cdae83d576225a13ba6e.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/58a818ecdf493bdf429b43577d5e0dc2.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/591a9b8114024a75acd15a69a56a36fe.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/59b5b4ff425760d788de7093ce1d1012.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/60cc1d01b97c8ac7b14ceda85a73ab13.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/62c20838fc028068799f97366df37c32.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/6391d14a5ed6ae83d652f83a83defa79.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/67b39362f4ce26443b57dfaeacb5fd8c.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/68c4f6cf28893508f98e4b5bf9ff28ac.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/68c51992808745781f7532d8a3fa0417.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/6914136f0f4bc47485c1627e93b0773a.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/6932e16e59c9650ade8c08e16182a757.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/69ecfd426806e6de6f9ce0727f4565b8.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/6a0d27f50d2141c4dfd652e9c23b6249.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/6dad15202f717570df710771e4d182d3.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/6dfad55f9e52accb7668cfb284a65d9f.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/6e3dd53515559145c1932171f37f6c85.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/74a1629b0e541dbe56e0b7770ee34924.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/760be58e1df5472a6f13bb259b877208.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/76b43a7f4c1cde9c4286f490e24edc78.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/78f489c41abe09a16c3999d0c9a7b90a.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/794eeac050b6da74abb391c84729803c.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/7e2610e60d485f5e81998ca7777fcf9c.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/80952741bd57cc37760ac68a9fef8bfa.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/80fef56cd659bc071c78637717fbaa64.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/81bc20af89a00b1516eb69e63101ee9d.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/82c19e9589324b024c72c173b4157825.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/83738994ebdb979a51013f2a1acb6d23.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/83a0e097e4a064645ac53d22d8d32f57.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/897152539e1e68f876230e2fa395b34d.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/8a78f2d94146de3c6e86238958898a1f.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/8bb0255b4a18e25b0544ea4a072f5820.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/8c650125236513164783e414b7ad704e.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/8ee10f424a29294196c03749e85b0abc.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/8fc3361106da1653b29719258376957c.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/9025d7c8e089a1b6063f9bb1e8c171bb.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/91fbcc6afc33ccd312c9e10c3efe3acc.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/930748dee49d2efcdfe0fa86e4b6e1cd.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/932a94c019d1df1e7033f45da20debd1.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/946b440586ba8edd45cc62c7c73ff051.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/94732072f2d7fd6c1bf16830407f406e.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/96463fad2109b8df3d81bbf9e3c6d1fe.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/976a8d24d813ec4dd370923b69797b0a.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/97e1520b374b1dce5179c5167917b402.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/99c1d2c9e8853173e37a694e9374dbc4.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/9d3e5b6eac4f0076b56b829ae9936bd2.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/a05a4b96e3d6bcfb9770c6d8b1e1725d.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/a283da15ef833cdf2aa43511a37a61ff.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/a38e2bae818a04aa03245f2f91945cf4.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/a566d0edc64bd1b21bda0204e5721004.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/a7133839fbe8b375d9e0722222bf1c52.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/a8acdb9a06199607b6e7db02a8a916cd.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/a9a233eb4c6e19fa7ab643326d0c4863.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/ab5242884a3bafbde07c3725abde4981.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/ad63e2f18f11a961a488eec2e7133408.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/b2c1478d8a719540ed095f7f11901bf0.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/b2e1f3fc54cd8d74fe9508db75eb9c22.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/b4ece08cc70adaad00f181a6f07b5d96.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/b5908104bb552819f6c39a6dfa6d433a.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/b6978a8d683be7911c472269a4dc3d61.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/b7643d4a6c0b059fe8a5ca4d36fa4a86.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/b87e59a7583c9e130d87b20d542c75b2.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/b900db42537f2c9d22dcc0e0216e31eb.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/b9729a74bbc44982d6fc3a78a0aef779.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/b997536256ad1b6b459d068d6a0babd4.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/c22729153a44085519514bb181d0957f.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/c536b1a0db47deef57a0a8ed691717c4.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/c563dd8b19108653f8cbcfbd33435dd7.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/c919d0bf5179593719bc6995f6b85f03.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/c922ea9e2a84ac61111bff75d0f0e55a.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/cb6def30172a2fc13ae943b6babe16c2.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/cb85f7be6c740b0d63d72c972e3260c6.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/cc7020466284e0e51594945ab49a7d45.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/cfcb7a0b918e0c38bd6ff2cdfa55ce0b.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/d275faf2bbbc445d136faef02f7d0a80.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/d2ca279535305825ccc4670ac0b6c651.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/d475b670fccf3395e15e55ee095d30b3.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/dbda7c0f0b0497b092f50372c335d1dc.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/dbf80daebe7e2c89ea99ffaaaa0dd9f6.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/dd3c65a0acf864c8433bb4770426f087.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/e03dceb8c532cebe2c262e55a1b904cf.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/e1e551c0f905a21f984466917faf17ad.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/e2d205c8413914b96ff60cab318bd968.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/e643e1c7f66039b470d916076a908f83.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/e66a29e747d865be4586284c055acd8e.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/e6d483741efe168fc2fc86282dc8f888.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/e7707da852fceb915a1bbf17ff07f0e4.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/e99adf6d03a9917be095b0fcd5d09ee3.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/ebffcc5e2c9dd811acd6e437d226ab19.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/ec56d2ee18bfbe47ffc9798825050a86.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/eebc3fee9ade1b687497a58fe234613c.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/f0d664804bc1618b11de47db19eeba69.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/f2987ba3b5619f7da9ac76a513be8139.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/f2e6da8323bf0d9d3a8eb868016b7d04.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/f42944f4f16ee88911a550a140417920.webm" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/videos/ff11f1f79d6d435f527004f282f4779b.webm" beforeDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Python Script" />
</list>
</option>
</component>
<component name="FlaskConsoleOptions" custom-start-script="import sys&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;from flask.cli import ScriptInfo&#10;locals().update(ScriptInfo(create_app=None).load_app().make_shell_context())&#10;print(&quot;Python %s on %s\nApp: %s [%s]\nInstance: %s&quot; % (sys.version, sys.platform, app.import_name, app.env, app.instance_path))">
<envs>
<env key="FLASK_APP" value="app" />
</envs>
<option name="myCustomStartScript" value="import sys&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;from flask.cli import ScriptInfo&#10;locals().update(ScriptInfo(create_app=None).load_app().make_shell_context())&#10;print(&quot;Python %s on %s\nApp: %s [%s]\nInstance: %s&quot; % (sys.version, sys.platform, app.import_name, app.env, app.instance_path))" />
<option name="myEnvs">
<map>
<entry key="FLASK_APP" value="app" />
</map>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
<option name="RESET_MODE" value="MIXED" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 5
}</component>
<component name="ProjectId" id="2dWOuVZ0QpUy3NwrP56qBz4oOjS" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"ASKED_ADD_EXTERNAL_FILES": "true",
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"SHARE_PROJECT_CONFIGURATION_FILES": "true",
"WebServerToolWindowFactoryState": "false",
"git-widget-placeholder": "main",
"ignore.virus.scanning.warn.message": "true",
"last_opened_file_path": "D:/CMS2.0_UI/CMS2.0_UI_AUTOMATION_TEST/Utils2",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "advanced.settings",
"vue.rearranger.settings.migration": "true"
}
}]]></component>
<component name="PyConsoleOptionsProvider">
<option name="myPythonConsoleState">
<console-settings module-name="CMS2.0_UI_AUTO_TEST" is-module-sdk="true">
<option name="myUseModuleSdk" value="true" />
<option name="myModuleName" value="CMS2.0_UI_AUTO_TEST" />
</console-settings>
</option>
</component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="D:\CMS2.0_UI\CMS2.0_UI_AUTOMATION_TEST\Utils2" />
<recent name="D:\CMS2.0_UI\CMS2.0_UI_AUTOMATION_TEST" />
<recent name="D:\CMS2.0_UI\CMS2.0_UI_AUTOMATION_TEST\Config2" />
<recent name="D:\CMS2.0_UI\CMS2.0_UI_AUTOMATION_TEST\Pages2" />
<recent name="D:\CMS2.0_UI\CMS2.0_UI_AUTOMATION_TEST\Common2" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="D:\CMS2.0_UI\CMS2.0_UI_AUTOMATION_TEST\Utils" />
<recent name="$PROJECT_DIR$/Common" />
<recent name="$PROJECT_DIR$/TestCases" />
</key>
</component>
<component name="RunManager" selected="Python.runner">
<configuration name="Config" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="CMS2.0_UI_AUTO_TEST" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/Config.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="conftest" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="CMS2.0_UI_AUTO_TEST" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/conftest.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="logger_util" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="CMS2.0_UI_AUTO_TEST" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/Utils" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/Utils/logger_util.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="runner" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="CMS2.0_UI_AUTO_TEST" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/runner.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="Python tests for ficture.TestMyClass.test_method_two" type="tests" factoryName="Autodetect" temporary="true" nameIsGenerated="true">
<module name="CMS2.0_UI_AUTO_TEST" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/TestCases" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="_new_additionalArguments" value="&quot;&quot;" />
<option name="_new_target" value="&quot;ficture.TestMyClass.test_method_two&quot;" />
<option name="_new_targetType" value="&quot;PYTHON&quot;" />
<method v="2" />
</configuration>
<recent_temporary>
<list>
<item itemvalue="Python.runner" />
<item itemvalue="Python.Config" />
<item itemvalue="Python.logger_util" />
<item itemvalue="Python tests.Python tests for ficture.TestMyClass.test_method_two" />
<item itemvalue="Python.conftest" />
</list>
</recent_temporary>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="0efa5718-fb1e-4917-aed6-7a6f1e1781c9" name="Changes" comment="" />
<created>1710120590873</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1710120590873</updated>
<workItem from="1710120591959" duration="12624000" />
<workItem from="1710205678870" duration="65041000" />
<workItem from="1710510828765" duration="72000" />
<workItem from="1710812164659" duration="1493000" />
<workItem from="1710942458916" duration="22191000" />
<workItem from="1711069677844" duration="24592000" />
<workItem from="1711335032467" duration="6113000" />
<workItem from="1711350329228" duration="8262000" />
<workItem from="1711415355837" duration="30341000" />
<workItem from="1711703146303" duration="11521000" />
<workItem from="1711849254570" duration="25058000" />
<workItem from="1712044102890" duration="8882000" />
<workItem from="1712480935294" duration="764000" />
<workItem from="1712543088728" duration="4843000" />
<workItem from="1712654647924" duration="3779000" />
<workItem from="1713880468082" duration="1982000" />
<workItem from="1714034436108" duration="32000" />
<workItem from="1715928266699" duration="1106000" />
<workItem from="1716451641806" duration="91000" />
<workItem from="1716530302091" duration="1126000" />
<workItem from="1719373778334" duration="13295000" />
<workItem from="1719459840302" duration="28714000" />
<workItem from="1719888670188" duration="20277000" />
<workItem from="1720082770825" duration="12512000" />
<workItem from="1720405723385" duration="18239000" />
<workItem from="1720489510798" duration="676000" />
<workItem from="1720490328665" duration="37484000" />
<workItem from="1721031733872" duration="12204000" />
<workItem from="1721112971666" duration="119000" />
<workItem from="1721113283817" duration="54581000" />
<workItem from="1721356250112" duration="23032000" />
<workItem from="1721613091346" duration="22743000" />
<workItem from="1721697285144" duration="27210000" />
<workItem from="1721784666304" duration="1268000" />
<workItem from="1721786929330" duration="10533000" />
<workItem from="1721893522894" duration="5078000" />
<workItem from="1722247533981" duration="9507000" />
<workItem from="1723000685126" duration="632000" />
<workItem from="1723016721374" duration="47362000" />
<workItem from="1723426867526" duration="22429000" />
<workItem from="1723471090079" duration="6985000" />
<workItem from="1723510029979" duration="23564000" />
<workItem from="1723599569471" duration="27514000" />
</task>
<task id="LOCAL-00001" summary="对框架内容进行调整">
<option name="closed" value="true" />
<created>1710121971024</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1710121971024</updated>
</task>
<task id="LOCAL-00002" summary="增加API请求工具类">
<option name="closed" value="true" />
<created>1710152633086</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1710152633086</updated>
</task>
<task id="LOCAL-00003" summary="1.增加日志打印工具类;&#10;2.config.py中增加pytest_fixture,在用例执行前导入测试工程,用例执行完毕后,删除测试工程;">
<option name="closed" value="true" />
<created>1710325409319</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1710325409319</updated>
</task>
<task id="LOCAL-00004" summary="1.增加OCR文字识别功能">
<option name="closed" value="true" />
<created>1710812338067</created>
<option name="number" value="00004" />
<option name="presentableId" value="LOCAL-00004" />
<option name="project" value="LOCAL" />
<updated>1710812338067</updated>
</task>
<task id="LOCAL-00005" summary="3月21日提交">
<option name="closed" value="true" />
<created>1711017965772</created>
<option name="number" value="00005" />
<option name="presentableId" value="LOCAL-00005" />
<option name="project" value="LOCAL" />
<updated>1711017965772</updated>
</task>
<task id="LOCAL-00006" summary="增加图像对比断言功能">
<option name="closed" value="true" />
<created>1712485888602</created>
<option name="number" value="00006" />
<option name="presentableId" value="LOCAL-00006" />
<option name="project" value="LOCAL" />
<updated>1712485888602</updated>
</task>
<task id="LOCAL-00007" summary="1.对自动化框架进行优化;&#10;2.增加用例操作回放视频(以附件方式存放在allure报告中);">
<option name="closed" value="true" />
<created>1721714941819</created>
<option name="number" value="00007" />
<option name="presentableId" value="LOCAL-00007" />
<option name="project" value="LOCAL" />
<updated>1721714941819</updated>
</task>
<task id="LOCAL-00008" summary="增加框架使用说明">
<option name="closed" value="true" />
<created>1721715698826</created>
<option name="number" value="00008" />
<option name="presentableId" value="LOCAL-00008" />
<option name="project" value="LOCAL" />
<updated>1721715698826</updated>
</task>
<task id="LOCAL-00009" summary="框架文件更新">
<option name="closed" value="true" />
<created>1721799360837</created>
<option name="number" value="00009" />
<option name="presentableId" value="LOCAL-00009" />
<option name="project" value="LOCAL" />
<updated>1721799360837</updated>
</task>
<task id="LOCAL-00010" summary="框架文件更新">
<option name="closed" value="true" />
<created>1721799675319</created>
<option name="number" value="00010" />
<option name="presentableId" value="LOCAL-00010" />
<option name="project" value="LOCAL" />
<updated>1721799675319</updated>
</task>
<task id="LOCAL-00011" summary="增加新的测试用例">
<option name="closed" value="true" />
<created>1722330205914</created>
<option name="number" value="00011" />
<option name="presentableId" value="LOCAL-00011" />
<option name="project" value="LOCAL" />
<updated>1722330205914</updated>
</task>
<task id="LOCAL-00012" summary="增加新的测试用例">
<option name="closed" value="true" />
<created>1722331431319</created>
<option name="number" value="00012" />
<option name="presentableId" value="LOCAL-00012" />
<option name="project" value="LOCAL" />
<updated>1722331431319</updated>
</task>
<task id="LOCAL-00013" summary="增加新的测试用例">
<option name="closed" value="true" />
<created>1722331524651</created>
<option name="number" value="00013" />
<option name="presentableId" value="LOCAL-00013" />
<option name="project" value="LOCAL" />
<updated>1722331524651</updated>
</task>
<task id="LOCAL-00014" summary="增加新的测试用例">
<option name="closed" value="true" />
<created>1722331533697</created>
<option name="number" value="00014" />
<option name="presentableId" value="LOCAL-00014" />
<option name="project" value="LOCAL" />
<updated>1722331533697</updated>
</task>
<task id="LOCAL-00015" summary="调整日志打印及删除部分框架非必要依赖">
<option name="closed" value="true" />
<created>1723019221066</created>
<option name="number" value="00015" />
<option name="presentableId" value="LOCAL-00015" />
<option name="project" value="LOCAL" />
<updated>1723019221066</updated>
</task>
<task id="LOCAL-00016" summary="调整日志打印及删除部分框架非必要依赖">
<option name="closed" value="true" />
<created>1723019730509</created>
<option name="number" value="00016" />
<option name="presentableId" value="LOCAL-00016" />
<option name="project" value="LOCAL" />
<updated>1723019730509</updated>
</task>
<task id="LOCAL-00017" summary="更新README文件">
<option name="closed" value="true" />
<created>1723020567127</created>
<option name="number" value="00017" />
<option name="presentableId" value="LOCAL-00017" />
<option name="project" value="LOCAL" />
<updated>1723020567127</updated>
</task>
<task id="LOCAL-00018" summary="1.修改工程ID为自动化获取&#10;2.BasePage.py中增加对鼠标拖动的支持&#10;3.PageManagementPage.py中增加页面对象公共属性操作的封装&#10;4.增加图像比对断言的方法&#10;5.更新README文件">
<option name="closed" value="true" />
<created>1723684334809</created>
<option name="number" value="00018" />
<option name="presentableId" value="LOCAL-00018" />
<option name="project" value="LOCAL" />
<updated>1723684334809</updated>
</task>
<option name="localTasksCounter" value="19" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="对框架内容进行调整" />
<MESSAGE value="增加API请求工具类" />
<MESSAGE value="1.增加日志打印工具类;&#10;2.config.py中增加pytest_fixture,在用例执行前导入测试工程,用例执行完毕后,删除测试工程;" />
<MESSAGE value="1.增加OCR文字识别功能" />
<MESSAGE value="3月21日提交" />
<MESSAGE value="增加图像对比断言功能" />
<MESSAGE value="1.对自动化框架进行优化;&#10;2.增加用例操作回放视频(以附件方式存放在allure报告中);" />
<MESSAGE value="增加框架使用说明" />
<MESSAGE value="框架文件更新" />
<MESSAGE value="增加新的测试用例" />
<MESSAGE value="调整日志打印及删除部分框架非必要依赖" />
<MESSAGE value="更新README文件" />
<MESSAGE value="1.修改工程ID为自动化获取&#10;2.BasePage.py中增加对鼠标拖动的支持&#10;3.PageManagementPage.py中增加页面对象公共属性操作的封装&#10;4.增加图像比对断言的方法&#10;5.更新README文件" />
<option name="LAST_COMMIT_MESSAGE" value="1.修改工程ID为自动化获取&#10;2.BasePage.py中增加对鼠标拖动的支持&#10;3.PageManagementPage.py中增加页面对象公共属性操作的封装&#10;4.增加图像比对断言的方法&#10;5.更新README文件" />
</component>
<component name="com.intellij.coverage.CoverageDataManagerImpl">
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$cv2_demo.coverage" NAME="cv2_demo Coverage Results" MODIFIED="1721178347208" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$HTTPClient.coverage" NAME="HTTPClient Coverage Results" MODIFIED="1723608054063" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Utils" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$runner.coverage" NAME="runner Coverage Results" MODIFIED="1723086341764" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$OCRTool.coverage" NAME="OCRTool Coverage Results" MODIFIED="1710489221687" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Utils" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$.coverage" NAME=" Coverage Results" MODIFIED="1723688359301" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/TestCases" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$cv2_demo1.coverage" NAME="cv2_demo1 Coverage Results" MODIFIED="1721181483234" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$APIRequestTool.coverage" NAME="APIRequestTool Coverage Results" MODIFIED="1710231419426" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Utils" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$BuildInLibrary.coverage" NAME="BuildInLibrary Coverage Results" MODIFIED="1723165799970" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/BuildInLibrary" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$Get_Screenshot.coverage" NAME="ScreenCapture Coverage Results" MODIFIED="1723453637933" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Common" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$ImageComparison.coverage" NAME="ImageComparison Coverage Results" MODIFIED="1723457336156" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Common" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$demo4.coverage" NAME="demo4 Coverage Results" MODIFIED="1723512035145" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Common" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$ReadYaml.coverage" NAME="ReadYaml Coverage Results" MODIFIED="1710405421794" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Utils" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$demo1.coverage" NAME="demo1 Coverage Results" MODIFIED="1723478794261" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Common" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$logger_util.coverage" NAME="logger_util Coverage Results" MODIFIED="1710319212505" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Utils" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$demo3.coverage" NAME="demo3 Coverage Results" MODIFIED="1723511346265" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Common" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$GetObjectPath_Util.coverage" NAME="GetObjectPath Coverage Results" MODIFIED="1710405633071" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Utils" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$CaptureScreen.coverage" NAME="CaptureScreen Coverage Results" MODIFIED="1721266773611" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Common" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$ImageRecognitionClick.coverage" NAME="ImageRecognitionClick Coverage Results" MODIFIED="1723175953135" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Common" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$apitest.coverage" NAME="apitest Coverage Results" MODIFIED="1710234382394" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Utils" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$Config.coverage" NAME="Config Coverage Results" MODIFIED="1710405856075" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Config" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$demo.coverage" NAME="demo Coverage Results" MODIFIED="1723477697751" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Common" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$VisualRegression.coverage" NAME="VisualRegression Coverage Results" MODIFIED="1710945676721" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Utils" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$ScreenshotComparison.coverage" NAME="ScreenshotComparison Coverage Results" MODIFIED="1723094702637" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Utils" />
<SUITE FILE_PATH="coverage/CMS2_0_UI_AUTO_TEST$VisualRegression_bak.coverage" NAME="VisualRegression_bak Coverage Results" MODIFIED="1711855426267" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Utils" />
</component>
</project>
\ No newline at end of file
# -*- coding:utf-8 -*-
import os
import random
import re
import time
import allure
import pytest
from playwright.sync_api import expect, Page
from BuildInLibrary.BuildInLibrary import BuildInLibrary
from Common.AllurePretty import PrettyAllure
from Config import Config
from Common.ImageComparison import ImageComparisonTool
from Utils.Debug_talk import DubugTalk
from Utils.logger_util import write_log, error_log
class BasePage:
def __init__(self, page: Page):
self.page = page
self.project_id = DubugTalk().read_extract_file("project_id")
# 工程管理url
self.project_management_url = Config.url
# 变量管理url
self.variable_management_url = Config.url + f"/#/editing/project/{self.project_id}/vars"
# 历史管理url
self.history_management_url = Config.url + f"/#/editing/project/{self.project_id}/history"
# 基础管理url
self.basis_management_url = Config.url + f"/#/editing/project/{self.project_id}/basis"
# 数据管理url
self.data_management_url = Config.url + f"/#/editing/project/{self.project_id}/data"
# 画面管理url
# self.page_management_url ="http://127.0.0.1:18000/#/editing/project/66/page"
self.page_management_url = Config.url + f"/#/editing/project/{self.project_id}/page"
# 报警管理url
self.alarm_management_url = Config.url + f"/#/editing/project/{self.project_id}/alarm"
# 多语言管理url
self.language_management_url = Config.url + f"/#/editing/project/{self.project_id}/lang"
# 外设管理url
self.device_management_url = Config.url + f"/#/editing/project/{self.project_id}/externalDevice"
# 互联管理url
self.connection_management_url = Config.url + f"/#/editing/project/{self.project_id}/connection"
# 自动化管理url
self.automation_management_url = Config.url + f"/#/editing/project/{self.project_id}/automation"
def _goto_url(self, value):
"""打开网页"""
if value == "工程":
self.page.goto(self.project_management_url)
elif value == "画面":
self.page.goto(self.page_management_url)
elif value == "变量":
self.page.goto(self.variable_management_url)
elif value == "历史":
self.page.goto(self.history_management_url)
elif value == "基础":
self.page.goto(self.basis_management_url)
elif value == "数据":
self.page.goto(self.data_management_url)
elif value == "报警":
self.page.goto(self.alarm_management_url)
elif value == "多语言":
self.page.goto(self.language_management_url)
elif value == "外设":
self.page.goto(self.device_management_url)
elif value == "互联":
self.page.goto(self.connection_management_url)
elif value == "自动化":
self.page.goto(self.automation_management_url)
# # 进入工程管理页面
# def _goto_url_project_management(self):
# project_management_url = Config.url
# self._goto_url(project_management_url)
#
# # 进入变量管理页面
# def _goto_url_variable_management(self):
# # 拼接变量管理访问地址
# variable_management_url = Config.url + f"/#/editing/project/{self.project_id}/vars"
# self._goto_url(variable_management_url)
#
# # 进入历史库页面
# def _goto_url_history_management(self):
# # 拼接历史库访问地址
# history_management_url = Config.url + f"/#/editing/project/{self.project_id}/history"
# self._goto_url(history_management_url)
#
# # 进入页面管理页面
# def _goto_url_page_management(self):
# # page_management_url = "http://127.0.0.1:18000/#/editing/project/127/page"
# page_management_url = Config.url + f"/#/editing/project/{self.project_id}/page"
# self._goto_url(page_management_url)
#
# # 进入基础页面
# def _goto_url_basis_management(self):
# # 拼接基础页面访问地址
# basis_management_url = Config.url + f"/#/editing/project/{self.project_id}/basis"
# self._goto_url(basis_management_url)
#
# # 进入数据管理页面
# def _goto_url_data_management(self):
# # 拼接数据管理访问地址
# data_management_url = Config.url + f"/#/editing/project/{self.project_id}/data"
# self._goto_url(data_management_url)
#
# # 进入报警管理页面
# def _goto_url_alarm_management(self):
# # 拼接报警管理访问地址
# alarm_management_url = Config.url + f"/#/editing/project/{self.project_id}/alarm"
# self._goto_url(alarm_management_url)
#
# # 进入多语言管理页面
# def _goto_url_language_management(self):
# # 拼接多语言管理访问地址
# language_management_url = Config.url + f"/#/editing/project/{self.project_id}/lang"
# self._goto_url(language_management_url)
#
# # 进入外设管理页面
# def _goto_url_device_management(self):
# # 拼接外设管理访问地址
# device_management_url = Config.url + f"/#/editing/project/{self.project_id}/externalDevice"
# self._goto_url(device_management_url)
#
# # 进入互联管理页面
# def _goto_url_connection_management(self):
# # 拼接互联管理访问地址
# connection_management_url = Config.url + f"/#/editing/project/{self.project_id}/connection"
# self._goto_url(connection_management_url)
#
# # 进入自动化管理页面
# def _goto_url_automation_management(self):
# # 拼接自动化管理访问地址
# automation_management_url = Config.url + f"/#/editing/project/{self.project_id}/automation"
# self._goto_url(automation_management_url)
def _click(self, comm_locator=None, frame_locator=None, pw_locator=None):
"""
点击元素
:param comm_locator: 传入普通元素定位器
:param frame_locator: 传入frame框架的的定位器
:param pw_locator: 传入playwright自带的定位器
:return:
"""
try:
if comm_locator is not None:
self.page.click(comm_locator)
if frame_locator is not None:
if comm_locator is not None:
self.page.frame_locator(frame_locator).locator(comm_locator).click()
if pw_locator is not None:
return pw_locator.click()
except Exception as e:
error_log(e)
def _hover(self, comm_locator=None, frame_locator=None, pw_locator=None):
"""
点击元素
:param comm_locator: 传入普通元素定位器
:param frame_locator: 传入frame框架的的定位器
:param pw_locator: 传入playwright自带的定位器
:return:
"""
try:
if frame_locator is not None:
if comm_locator is not None:
self.page.frame_locator(frame_locator).locator(comm_locator).hover()
if comm_locator is not None:
self.page.hover(comm_locator)
if pw_locator is not None:
pw_locator.hover()
except Exception as e:
error_log(e)
def _fill(self, value, comm_locator=None, frame_locator=None, pw_locator=None):
"""
定位元素,输入内容
:param comm_locator:传入普通元素定位器
:param value:传入输入的值
:param frame_locator: 传入frame框架
:param pw_locator: 传入playwright自带的定位器
:return:
"""
if not isinstance(value, str):
value = str(value)
else:
value = value
try:
if frame_locator is not None:
if comm_locator is not None:
self.page.frame_locator(selector=frame_locator).locator(selector_or_locator=comm_locator).fill(
value)
if comm_locator is not None:
self.page.fill(selector=comm_locator, value=value)
if pw_locator is not None:
pw_locator.fill(value)
except Exception as e:
error_log(e)
def _press(self, comm_locator=None, frame_locator=None, pw_locator=None):
"""
定位元素,回车键
:param comm_locator:传入普通元素定位器
:param value:传入输入的值
:param frame_locator: 传入frame框架
:param pw_locator: 传入playwright自带的定位器
:return:
"""
try:
if frame_locator is not None:
if comm_locator is not None:
self.page.frame_locator(selector=frame_locator).locator(selector_or_locator=comm_locator).press("Enter")
if comm_locator is not None:
self.page.press("Enter")
if pw_locator is not None:
pw_locator.press("Enter")
except Exception as e:
error_log(e)
def _type(self, value, comm_locator=None, pw_locator=None, frame_locator=None):
"""
模拟人工输入,一个键一个键的输入
:param comm_locator:传入普通元素定位器
:param value:传入输入的值
:param frame_locator: 传入frame框架
:param pw_locator: 传入playwright自带的定位器
:return:
"""
value = BuildInLibrary().repalce_parameter(value)
try:
if frame_locator is not None:
if comm_locator is not None:
self.page.frame_locator(selector=frame_locator).locator(selector_or_locator=comm_locator).type(
text=value, delay=100)
if comm_locator is not None:
self.page.type(selector=comm_locator, text=value, delay=100)
if pw_locator is not None:
# 先清空原有的值
pw_locator.fill("")
# 输入新的值
pw_locator.type(text=value, delay=100)
except Exception as e:
error_log(e)
def _scroll_into_view_if_needed(self, comm_locator=None, frame_locator=None, pw_locator=None):
"""
滚动到指定元素位置
:param comm_locator: 传入普通元素定位器
:param frame_locator: 传入frame框架的的定位器
:param pw_locator: 传入playwright自带的定位器
:return:
"""
try:
if frame_locator is not None:
if comm_locator is not None:
self.page.frame_locator(frame_locator).locator(comm_locator).scroll_into_view_if_needed()
if pw_locator is not None:
return pw_locator.scroll_into_view_if_needed()
except Exception as e:
error_log(e)
def _file(self, files, comm_locator=None, pw_locator=None, frame_locator=None):
"""
上传文件的方法
:param comm_locator: 普通定位器
:param files: 单个文件名,或者列表存放多个文件
:param frame_locator: iframe框架定位器,如果没有就不传
:param pw_locator: playwright自带的定位器
:return:
"""
try:
if frame_locator is not None:
if comm_locator is not None:
self.page.frame_locator(frame_locator).locator(comm_locator).set_input_files(files=files)
if comm_locator is not None:
self.page.locator(comm_locator).set_input_files(files=files)
if pw_locator is not None:
pw_locator.set_input_files(files=files)
except Exception as e:
error_log(e)
def _ele_to_be_visible(self, locator):
"""断言页面元素可见"""
return expect(self.page.locator(locator)).to_be_visible()
def _ele_to_be_visible_force(self, locator, frame_locator=None, timout: int = 5):
"""强制等待某个元素可见"""
ele = None
if frame_locator is not None:
ele = self.page.frame_locator(frame_locator).locator(locator)
else:
ele = self.page.locator(locator)
for t in range(0, timout):
self.page.wait_for_timeout(500)
if ele.is_visible():
break
else:
raise Exception("元素未找到!")
def _ele_is_checked(self, selector):
"""判断元素是否被选中"""
return self.page.is_checked(selector)
def _ele_to_contain_text(self, selector, substring):
"""判断元素是否包含指定的子文本"""
return expect(self.page.locator(selector)).to_contain_text(substring)
def _ele_to_have_text(self, selector, text):
"""判断元素是否包含指定的文本"""
return expect(self.page.locator(selector)).to_have_text(re.compile(rf"{text}"))
def _browser_operation(self, reload=False, forward=False, back=False, close=False):
"""浏览器操作,reload 刷新,forward 前进,back 后退"""
if reload:
self.page.reload()
if back:
self.page.go_back()
if forward:
self.page.go_forward()
if close:
self.page.close()
def screenshot(self, path, full_page=True, locator=None):
"""截图功能,默认截取全屏,如果传入定位器表示截取元素"""
if locator is not None:
self.page.locator(locator).screenshot(path=path)
return path
self.page.screenshot(path=path, full_page=full_page)
return path
# def _del_auth(self):
# """删除授权文件"""
# auth_path = Config.auth_dir + os.path.sep + "auth.json"
# if os.path.exists(auth_path):
# os.remove(auth_path)
def _get_one_element_from_list_by_random(self, parent_locator):
"""从列表中随机获取一个元素"""
# 获取包含列表元素的父元素
list_parent = self.page.query_selector(parent_locator)
# 获取所有的列表元素
list_items = list_parent.query_selector_all("li")
# 从列表元素中随机选择一个元素
random_index = random.randint(0, len(list_items) - 1)
return list_items[random_index]
def _drag_and_drop_to_user_position(self, source_selector, target_point_x, target_point_y):
"""
拖拽元素到用户指定的位置,target_point_x和target_point_y为页面目标点的横纵坐标,
需要用户自己计算(可使用一些屏幕坐标点测试工具进行测量,如:snipaste)
source_selector: 拖拽元素的定位器
target_point_x: 目标点的横坐标
target_point_y: 目标点的纵坐标
"""
if source_selector is not None:
# 获取元素的边界信息,用于确定拖拽的起始点
source_box = source_selector.bounding_box()
if source_box is None:
raise Exception("无法获取元素边界信息")
# 计算拖拽的起始点(中心点)
source_point = {
'x': source_box['x'] + source_box['width'] / 2,
'y': source_box['y'] + source_box['height'] / 2
}
# 执行拖拽操作到用户指定的位置
self.page.mouse.move(source_point['x'], source_point['y'])
self.page.mouse.down()
# 移动鼠标到目标位置
self.page.mouse.move(target_point_x, target_point_y)
# 释放鼠标按钮
self.page.mouse.up()
def compare_images(self,filename,actual_picture,expect_picture,screenshot_area="editor"):
actual_img = Config.img_dir + os.path.sep + filename + os.path.sep + "actual_img" + os.path.sep + f"{actual_picture}.png" # actual_img_dir为存储实际截图的路径
expect_img = Config.img_dir + os.path.sep + filename + os.path.sep + "expected_img" + os.path.sep + f"{expect_picture}.png" # expected_img_dir为存储预期截图的路径
crate_expect_img_path = Config.img_dir + os.path.sep + filename + os.path.sep + "expected_img"
# compare_result_img = Config.result_img_dir + os.path.sep + f"{result_picture}.png" # result_img_dir为存储对比结果截图的路
# 运行版截图
try:
os.makedirs(crate_expect_img_path)
print(f"Directory '{crate_expect_img_path}' created successfully")
except FileExistsError:
print(f"Directory '{crate_expect_img_path}' already exists")
if screenshot_area == "vision":
x = Config.vision_x_coordinate
y = Config.vision_y_coordinate
w = Config.vision_width
h = Config.vision_height
# 开发版截图
elif screenshot_area == "editor":
x = Config.editor_x_coordinate
y = Config.editor_y_coordinate
w = Config.editor_width
h = Config.editor_height
# 自定义参数截图
else:
x = Config.custom_x_coordinate
y = Config.custom_x_coordinate
w = Config.custom_x_coordinate
h = Config.custom_x_coordinate
#指定区域截图
ScreenshotArea = {'x': x, 'y': y, 'width': w, 'height': h}
self.page.screenshot(path=actual_img, full_page=True, clip=ScreenshotArea)
comparator = ImageComparisonTool()
result = comparator.compare_images(actual_img, expect_img)
# 返回比对结果,true为相似,false为不相似
write_log("进行图片比对...")
write_log(f"实际图片:{actual_picture}.png, 预期图片:{expect_picture}.png", )
write_log(f"比对结果:{result}",)
return result
def allure_reports(self,filename,actual_img_name,expect_img_name):
actual_img = Config.img_dir + os.path.sep + filename + os.path.sep + "actual_img" + os.path.sep + f"{actual_img_name}.png" # actual_img_dir为存储实际截图的路径
expect_img = Config.img_dir + os.path.sep + filename + os.path.sep + "expected_img" + os.path.sep + f"{expect_img_name}.png" # expected_img_dir为存储预期截图的路径
with allure.step('实际截图'):
allure.attach.file(actual_img, f"{actual_img_name}", allure.attachment_type.PNG)
with allure.step('预期截图'):
allure.attach.file(expect_img, f"{expect_img_name}", allure.attachment_type.PNG)
# -*- coding:utf-8 -*-
"""
description: 内置库
author: wanchao
time: 2023/12/1
"""
import os
import random, time, re, uuid
class BuildInLibrary():
# 存全局变量
glob_parameter = {}
def set_glob_parameter(self, key, value):
"""把value的值放入变量名 key 中,"""
# 1.提取 key ##{{first_phone}}
parameter_key = re.fullmatch(r'{{(\w+)}}', key).group(1)
# 2.保存参数
self.glob_parameter[parameter_key] = value
return self.glob_parameter.get(parameter_key)
def get_glob_parameter(self, key):
self.glob_parameter['timestamp'] = str(int(time.time() * 1000))
self.glob_parameter["timetime"] = str(int(time.time()))
self.glob_parameter['random_phone'] = "1" + \
str(random.randint(3, 9)) + \
str(random.randint(0, 9)) + \
time.strftime('%d%H%M%S')
self.glob_parameter['uuid'] = str(uuid.uuid4())
return self.glob_parameter.get(key)
def repalce_parameter(self, text):
"""替换参数--> 可以替换多个~,满足{{$参数}}规则的会被替换"""
parameter_key = re.findall(r'{{\$(\w+)}}', text)
for param in parameter_key:
value = self.get_glob_parameter(param)
to = rf"{value}"
text = re.sub(rf'{{{{\${param}}}}}', lambda m: to, text)
return text
"""获取文件路径"""
def get_path(self, file_name):
current_path = os.path.dirname(os.path.dirname(__file__))
root_dir = os.path.abspath(current_path)
file = os.path.join(root_dir, "ProjectData", file_name)
return file
if __name__ == "__main__":
bl = BuildInLibrary()
bl.set_glob_parameter("{{username}}", "admin")
bl.set_glob_parameter("{{password}}", "123456")
username = bl.get_glob_parameter("username")
password = bl.get_glob_parameter("password")
text = "{{$username}} + {{$password}} + {{$timetime}} + {{$random_phone}} + {{$timestamp}} + {{$uuid}}"
t = bl.repalce_parameter(text)
print(t)
# -*- coding:utf-8 -*-
"""
describe:allure常用方法抽离,截图方法实现
Author:wangchao
Time: 2023/12/2
"""
import uuid
import allure
import functools
import os.path
import pytest
import time
from Config import Config
class PrettyAllure(object):
@classmethod
def PrettyAllureCase(cls, page, CaseData):
allure.dynamic.feature(CaseData.get("模块"))
allure.dynamic.story(CaseData.get("功能"))
allure.dynamic.severity(CaseData.get("优先级"))
allure.dynamic.title(f'{CaseData.get("用例编号")}_{CaseData.get("用例标题")}')
if CaseData.get("是否执行") != "Y":
allure.dynamic.description("用例指定跳过")
pytest.skip("用例指定跳过")
@classmethod
def PrettyAllureScreenShot(cls, pw_locator):
picture_name = f"元素操作截图{str(uuid.uuid4())}.png"
filename = os.path.join(Config.test_screenshot_dir, picture_name)
pw_locator.screenshot(path=filename)
allure.attach.file(source=filename, name=picture_name, attachment_type=allure.attachment_type.PNG)
@classmethod
def PrettyAllureVideo(cls, page, CaseData):
allure.dynamic.story(CaseData.get("功能"))
# 录制视频
video = page.video
video_name = f"用例操作录屏{str(uuid.uuid4())}.webm"
video_path = os.path.join(Config.test_video_dir, video_name)
video.save_as(video_path)
# 添加视频到Allure报告
with open(video_path, "rb") as video_file:
allure.attach(body=video_file.read(),
attachment_type=allure.attachment_type.MP4,
attachmentsubtype=None,
name=allure.dynamic.story(CaseData.get("功能")))
@classmethod
def PrettyAllureCaseWarpper(cls, func):
"""装饰器函数"""
@functools.wraps(func)
def inner(*args, **kwargs):
# 添加用例信息
cls.PrettyAllureCase(page=kwargs.get("page"), CaseData=kwargs.get("CaseData"))
# 运行用例
r = func(*args, **kwargs)
return r
return inner
@classmethod
def PrettyAllureScreenShotWarpper(cls, func):
"""装饰器函数"""
@functools.wraps(func)
def inner(*args, **kwargs):
# 运行用例
r = func(*args, **kwargs)
# 添加截图
cls.PrettyAllureScreenShot(pw_locator=kwargs.get("pw_locator"))
return r
return inner
@classmethod
def PrettyAllureVideoWarpper(cls, func):
"""装饰器函数"""
@functools.wraps(func)
def inner(*args, **kwargs):
# 运行用例
r = func(*args, **kwargs)
# 添加视频
cls.PrettyAllureVideo(page=kwargs.get("page"), CaseData=kwargs.get("CaseData"))
return r
return inner
if __name__ == '__main__':
pass
# Time : 2024/8/13 09:13
# Author : wangchao
# File : demo4.py
# Description :
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from Config import Config
from PIL import Image, ImageChops
class ImageComparisonTool:
def __init__(self):
#从配置文件获取阈值
self.similarity_threshold = Config.similarity_threshold
def compare_images(self, actual_image_path, expected_image_path):
"""
比较两个图片是否相似
:param actual_image_path: 实际图
:param expected_image_path: 预期图
:param compare_result_img_path: 比对结果图
:param highlight_differences:
:return:
"""
# 读取图片
img1 = Image.open(actual_image_path)
img2 = Image.open(expected_image_path)
# 确保两个图片具有相同的模式和大小
if img1.mode != img2.mode or img1.size != img2.size:
return False
# 计算差异
diff = ImageChops.difference(img1, img2)
# 将差异图片转换为灰度,并获取差异像素的总数
diff_gray = diff.convert('L')
squared_pixels = [i ** 2 for i in list(diff_gray.getdata())]
rmse = (sum(squared_pixels) / len(squared_pixels)) ** 0.5
# 根据均方根误差(RMSE)和阈值判断图片是否相似
return rmse < self.similarity_threshold
# 在测试用例中调用
if __name__ == '__main__':
comparator = ImageComparisonTool()
result = comparator.compare_images('Rectangle_actual.png', 'Rectangle_expected2.png')
if result:
print("测试通过:两张图片相似")
else:
print("测试失败:两张图片不相似")
\ No newline at end of file
# Time : 2024/8/9 10:11
# Author : wangchao
# File : ImageRecognitionClick.py
# Description :
import os
import time
import cv2
import numpy as np
from playwright.sync_api import sync_playwright
from Config import Config
class PlaywrightImageClicker:
def __init__(self, url):
self.url = url
def capture_screenshot(self, screenshot_path):
with sync_playwright() as p:
browser = p.chromium.launch(headless=False, channel="chromium", args=['--start-maximized'])
context = browser.new_context(no_viewport=True, locale="zh-CN", accept_downloads=True)
page = context.new_page()
page.goto(self.url)
time.sleep(5)
page.screenshot(path=screenshot_path, full_page=True)
browser.close()
def find_element_position(self, screenshot_path, template_path, threshold=0.8):
# 读取截图和模板图像
full_image = cv2.imread(screenshot_path)
template = cv2.imread(template_path)
# 转换为灰度图
gray_full = cv2.cvtColor(full_image, cv2.COLOR_BGR2GRAY)
gray_template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
# 使用模板匹配
result = cv2.matchTemplate(gray_full, gray_template, cv2.TM_CCOEFF_NORMED)
# 寻找匹配位置
loc = np.where(result >= threshold)
for pt in zip(*loc[::-1]):
return pt
return None
def click_element(self, element_position):
if element_position is None:
print("Element position not found. Cannot click.")
return
with sync_playwright() as p:
browser = p.chromium.launch(headless=False, channel="chromium", args=['--start-maximized'])
context = browser.new_context(no_viewport=True, locale="zh-CN", accept_downloads=True)
page = context.new_page()
page.goto(self.url)
time.sleep(5)
#x, y = element_position
# Convert x and y to standard Python integers
x, y = int(element_position[0]), int(element_position[1])
print(f"Clicking at ({x}, {y})")
page.mouse.click(x, y)
time.sleep(5)
browser.close()
def perform_image_click(self, screenshot_path, template_path):
# Step 1: Capture screenshot
self.capture_screenshot(screenshot_path)
# Step 2: Find element position
element_position = self.find_element_position(screenshot_file_path, template_path)
# Step 3: Click the element
self.click_element(element_position)
# Example usage
if __name__ == "__main__":
clicker = PlaywrightImageClicker('http://192.168.1.166:18000/#/editing/project/158/page/41')
screenshot_file_path = Config.screenshot_img_dir + os.path.sep + f"{str(int(time.time()))}屏幕截图.png"
template_file_path = Config.template_img_dir + os.path.sep + "Snipaste_2024-08-09_11-57-40.png"
clicker.perform_image_click(screenshot_file_path, template_file_path)
\ No newline at end of file
# Time : 2024/8/12 14:14
# Author : wangchao
# File : Get_Screenshot.py
# Description :
import functools
import os
import time
from playwright.sync_api import sync_playwright
from Config import Config
class ScreenCapture:
def __init__(self):
self.browser = None
self.page = None
def init(self):
playwright = sync_playwright().start()
self.browser = playwright.chromium.launch(headless=False, channel="chromium", args=['--start-maximized']) # 启动 Chromium 浏览器
context = self.browser.new_context(no_viewport=True, locale="zh-CN")
self.page = context .new_page() # 创建新的页面
def navigate(self, url):
self.page.goto(url) # 导航到指定的 URL
self.page.wait_for_load_state('networkidle') # 等待页面的所有网络请求结束并且在一段时间没有新的请求(默认是500毫秒)
#time.sleep(5)
def capture_screenshot(self, x, y, width, height, output_path):
# 根据指定区域截取快照
self.page.screenshot(path=output_path, clip={'x': x, 'y': y, 'width': width, 'height': height})
def close(self):
self.page.close() # 关闭页面
self.browser.close() # 关闭浏览器
def generate_screenshot(self, url, x, y, width, height, screenshot_file_path):
"""生成页面截图"""
try:
self.init()
self.navigate(url)
#screenshot_file_path = Config.actual_img_dir + os.path.sep + f"{case_name}_{str(int(time.time()))}屏幕截图.png" # actual_img_dir为存储实际截图的路径
self.capture_screenshot(x, y, width, height, screenshot_file_path)
finally:
self.close()
# 使用示例
if __name__ == "__main__":
screen_capture = ScreenCapture()
screenshot_file_path = Config.actual_img_dir + os.path.sep + f"测试用例2_页面截图_实际_{str(int(time.time()))}.png" # actual_img_dir为存储实际截图的路径
screen_capture.generate_screenshot('http://192.168.1.166:18000/#/editing/project/158/page/41', 345, 333, 865, 640, screenshot_file_path)
# -*- coding:utf-8 -*-
"""
describe:配置文件
Author:wangchao
Time: 2023/12/5
"""
import os
class Config:
# 浏览器类型定义
browser = "chromium"
# CMS web端访问地址
url = "http://127.0.0.1:18000"
# CMS token
CMS_TOKEN = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiU3VwZXJBZG1pbiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWVpZGVudGlmaWVyIjoiU3VwZXJBZG1pbiIsImlzcyI6IkNNU3YyIiwiYXVkIjoiQXBpIn0.AJUOE6YQ5MAxDqHOIz-yQbpCKPNq5tPoFBWUY381qns"
#project_id = "158"
# 项目目录定义
root_dir = os.path.abspath(os.getcwd().split('Config.py')[0])
# root_dir = os.path.split(os.path.split(__file__)[0])[0]
test_project_dir = root_dir + os.path.sep + "ProjectData"
test_files_dir = root_dir + os.path.sep + "TestFiles"
test_cases_dir = root_dir + os.path.sep + "TestCases"
test_datas_dir = root_dir + os.path.sep + "TestDatas"
test_report_dir = root_dir + os.path.sep + "TestReport" + os.path.sep + "AllureReport"
test_report_parent_dir = root_dir + os.path.sep + "TestReport"
test_result_dir = root_dir + os.path.sep + "TestReport" + os.path.sep + "AllureResult"
test_screenshot_dir = root_dir + os.path.sep + "TestReport" + os.path.sep + "Screenshot"
test_video_dir = root_dir + os.path.sep + "TestReport" + os.path.sep + "Videos"
logs = root_dir + os.path.sep + "Logs"
# 图像对比路径定义
img_dir = root_dir + os.path.sep + "ImageComparison"
# result_img_dir = root_dir + os.path.sep + "ImageComparison" + os.path.sep + "result_img"
# 图像识别路径定义
screenshot_img_dir = root_dir + os.path.sep + "ImageRecognition" + os.path.sep + "screenshot_img"
template_img_dir = root_dir + os.path.sep + "ImageRecognition" + os.path.sep + "template_img" + os.path.sep
# 日志相关配置
log_name = "console_log_"
log_level = "info"
log_format = '[%(asctime)s] %(filename)s->%(funcName)s line:%(lineno)d [%(levelname)s] %(message)s'
#开发版 截图范围
editor_x_coordinate = 340 # 左上角 x 坐标
editor_y_coordinate = 230 # 左上角 y 坐标
editor_width = 1100 # 区域宽度
editor_height = 600 # 区域高度
# 运行版 截图范围
vision_x_coordinate = 0 # 左上角 x 坐标
vision_y_coordinate = 0 # 左上角 y 坐标
vision_width = 1920 # 区域宽度
vision_height = 1080 # 区域高度
# 自定义截图范围
custom_x_coordinate = 340 # 左上角 x 坐标
custom_y_coordinate = 230 # 左上角 y 坐标
custom_width = 1100 # 区域宽度
custom_height = 600 # 区域高度
# 将组件拖到画布指定区域
target_point_x = 820 # 目标点横坐标
target_point_y = 450 # 目标点纵坐标
#图片比对的相似性阈值
similarity_threshold = 0.1
# def pr(self):
# print(self.root_dir)
# print(self.expected_img_dir)
#
# if __name__ == '__main__':
# Config().pr()
[2024-08-26 10:29:35,681] logger_util.py->write_log line:72 [INFO]
-------------测试用例开始-----------
[2024-08-26 10:29:42,335] logger_util.py->write_log line:72 [INFO] 进行图片比对...
[2024-08-26 10:29:42,335] logger_util.py->write_log line:72 [INFO] 实际图片:静态文本_设置右对齐_实际图.png, 预期图片:静态文本_设置右对齐_预期图.png
[2024-08-26 10:29:42,336] logger_util.py->write_log line:72 [INFO] 比对结果:True
[2024-08-26 10:29:42,592] logger_util.py->write_log line:72 [INFO]
-------------测试用例结束-----------
# Time : 2024/7/23 16:16
# Author : wangchao
# File : HistoryLibraryManagement.py
# Description :
from BasePage.BasePage import BasePage
class HistoryLibraryManagement(BasePage):
def __init__(self, page):
super().__init__(page)
"""
元素定位器
"""
# 数据备份
self.history_library_edite_pop_up_window_locator = page.locator("div:nth-child(2) > .custom-tree-node > .node > .btn > .iconfont") # 点击“历史组01”编辑菜单
self.history_library_backup_button_locator = page.get_by_text("数据备份") # 点击“历史组01”编辑菜单中的“数据备份”
self.history_library_backup_success_toast_locator = page.locator("//*[@id=\"message_1\"]") # 备份成功
# 数据清除
self.history_library_clear_button_locator = page.get_by_text("数据清除") # 点击“历史组01”编辑菜单中的“数据清除”
self.history_library_clear_success_toast_locator = page.locator("//*[@id=\"message_1\"]") # 清除成功
self.history_library_clear_confirm_button_locator = page.get_by_text("确定") # 确认清除
"""
页面操作
"""
# 历史库数据备份
def history_library_backup(self):
# 点击“历史组01”编辑菜单
self._click(pw_locator=self.history_library_edite_pop_up_window_locator)
# 点击“数据备份”
self._click(pw_locator=self.history_library_backup_button_locator)
# 历史库数据清除
def history_library_clear(self):
# 点击“历史组01”编辑菜单
self._click(pw_locator=self.history_library_edite_pop_up_window_locator)
self.page.on("dialog", lambda dialog: dialog.accept()) # 确定清除弹框处理
# 点击“数据清除”
self._click(pw_locator=self.history_library_clear_button_locator)
# 确认清除
self._click(pw_locator=self.history_library_clear_confirm_button_locator)
\ No newline at end of file
# Time : 2024/7/25 17:12
# Author : wangchao
# File : PageManagementPage.py
# Description :
import re
import time
from BasePage.BasePage import BasePage
from BuildInLibrary.BuildInLibrary import BuildInLibrary
from Config import Config
class PageManagementPage(BasePage):
def __init__(self, page, page_name=None):
super().__init__(page)
"""左侧画面列表定位器"""
# 新建页面
self.cms_page_new_page_button_locator = page.get_by_title("添加画面") # 点击新建页面按钮
# 画面导入/导出
self.cms_page_locator = page.locator("#app span").filter(has_text=f"{page_name}") # 点击待导出页面,页面名称为“画面导入导出测试”
self.cms_page_export_button_locator = page.get_by_title("导出画面") # 点击导出按钮
self.cms_page_import_button_locator = page.get_by_title("导入画面") # 点击导入按钮
self.cms_page_export_success_toast_locator = page.locator("//*[@id=\"message_1\"]") # 导出成功提示
self.cms_page_import_success_toast_locator = page.locator("//*[@id=\"message_1\"]") # 导入成功提示
"""左下页面对象定位器"""
# 画面对象-矩形
self.cms_page_object_rectangle_locator = page.locator("#app").get_by_text("矩形1") # 矩形对象
# 画面对象-线条
self.cms_page_object_line_locator = page.locator("#app").get_by_text("线条1") # 线条对象
# 画面对象-静态文本
self.cms_page_object_text_locator = page.locator("#app").get_by_text("文本1") # 文本对象
"""顶部菜单栏定位器"""
self.zoom_locator = page.locator("#app").get_by_text("%")
self.screen_adaptation= page.get_by_text("适应画布")
"""组件箱和组件定位器"""
# 标准组件_基础组件_矩形绘制
self.cms_page_component_locator = page.locator("//i[@class='icon-biaozhunkongjian']") # 标准组件图标
# 矩形
self.cms_page_component_rectangle_locator = page.get_by_label("基础").locator("div").filter(
has_text=re.compile(r"^矩形$"))
# 水平菜单
self.cms_page_component_static_text = page.get_by_label("基础").locator("div").filter(
has_text=re.compile(r"^静态文本$"))
# 趋势曲线配置
self.cms_page_component_change_to_runtime_component_locator = page.get_by_role("tab", name="运行") # 切换到运行组件tab
self.cms_page_component_trend_configuration_locator = page.get_by_label("运行").locator("div").filter(
has_text=re.compile(r"^趋势配置$")) # 趋势曲线组件
self.cms_page_component_add_curve_locator = page.locator(".add-new-btn") # 添加曲线组
"""通用属性-位置属性"""
# 定位X轴输入框
self.x_axis_locator = page.locator("div").filter(has_text=re.compile(r"^X$")).get_by_role("spinbutton")
# 定位Y轴输入框
self.y_axis_locator = page.locator("div").filter(has_text=re.compile(r"^Y$")).get_by_role("spinbutton")
# 定位旋转输入框
self.rotation = page.locator("div").filter(has_text=re.compile(r"^°$")).get_by_role("spinbutton")
# 定位W位置
self.w_locator = page.locator("div").filter(has_text=re.compile(r"^W$")).get_by_role("spinbutton")
# 定位H位置
self.h_locator = page.locator("div").filter(has_text=re.compile(r"^H$")).get_by_role("spinbutton")
# 定位锁定按钮
self.set_lock_locator = page.get_by_title("解锁").nth(2)
"""通用属性-外观属性"""
"颜色选择弹窗"
# 关闭颜色选择器窗口
self.color_selection_window_ok_button_locator = page.get_by_role("button", name="OK")
"背景"
# 定位纯色背景按钮
self.solid_color_background_locator = page.get_by_label("外观").locator("div").filter(has_text=re.compile(r"^背景$")).locator("span").nth(1)
# 定位红色快捷按钮
self.red_shortcut_button_locator = page.locator("div:nth-child(18) > div > .el-color-predefine > .el-color-predefine__colors > div:nth-child(4) > div")
# 定位渐变/图片按钮
self.gradient_and_picture_button_locator = page.get_by_label("外观").locator("use")
# 定位渐变色按钮
self.gradient_button_locator = page.get_by_label("外观").get_by_title("线性渐变")
# 定位添加图片按钮
self.add_image_locator = page.locator("//span[text()=\"添加图片\"]").nth(1)
# 定位图库中默认分组的第一张图片
self.image_locator = page.locator("//div[@class='component-gallery-material-list']//div[1]//div[1]//img[1]")
# 定位图片的下拉框按钮
self.image_select_button_locator = page.get_by_role("textbox", name="Select")
# 定位拉伸类型
self.image_stretch_type_locator = page.get_by_text("拉伸").nth(1)
# 定位自适应类型
self.image_adapt_type_locator = page.get_by_text("自适应").nth(1)
# 定位实际大小类型
self.image_actual_type_locator = page.get_by_text("实际大小").nth(1)
# 定位覆盖类型
self.image_cover_type_locator = page.get_by_text("覆盖").nth(1)
"边框"
# 定位边框类型的下拉框按钮
self.frame_type_button_locator = page.get_by_placeholder(" ")
# 定位直线类型
self.straight_line_frame_locator = page.get_by_text("直线")
# 定位圆点类型
self.circular_dots_frame_locator = page.get_by_text("圆点")
# 定位虚线类型
self.dashed_line_frame_locator = page.locator("div").filter(has_text=re.compile(r"^虚线$")).locator("span")
# 定位无边框类型
self.no_frame_locator = page.get_by_text("无", exact=True)
# 定位宽度输入框
self.frame_width_locator = page.locator("div").filter(has_text=re.compile(r"^边框$")).get_by_role("spinbutton")
# 定位边框颜色
self.frame_colour_locator = page.locator("div:nth-child(3) > .el-color-picker > .el-color-picker__trigger > .el-color-picker__color > .el-color-picker__color-inner").first
# 定位边框颜色-设置红色
self.frame_red_colour_locator = page.locator("div:nth-child(30) > div > .el-color-predefine > .el-color-predefine__colors > div:nth-child(4) > div")
"阴影"
# 定位外阴影按钮
self.external_shadow_button_locator = page.get_by_title("外阴影")
# 定位外阴影模糊度,25度
self.external_shadow_ambiguity_locator = page.get_by_role("slider", name="slider between 0 and 50").locator("div").first
# 定位外阴影颜色选择器按钮
self.color_selector_for_external_shadows_locator = page.get_by_role("tooltip", name="水平: 0 0 垂直: 0 0 模糊: 25 0 大小:").locator("span").nth(1)
# 定位外阴影颜色,红色
self.color_of_external_shadow_locator =page.locator("div:nth-child(37) > div > .el-color-predefine > .el-color-predefine__colors > div:nth-child(4) > div")
# 定位内阴影按钮
self.internal_shadow_button_locator = page.get_by_title("内阴影(可能被内部内容遮挡)")
# 定位内阴影模糊度,25度
self.internal_shadow_ambiguity_locator = page.get_by_role("slider", name="slider between 0 and 50").locator("div").first
# 定位内阴影颜色选择器按钮
self.color_selector_for_internal_shadows_locator = page.get_by_role("tooltip", name="水平: 0 0 垂直: 0 0 模糊: 25 0 大小:").locator("span").nth(1)
# 定位内阴影颜色,绿色
self.color_of_internal_shadow_locator = page.locator("div:nth-child(43) > div > .el-color-predefine > .el-color-predefine__colors > div:nth-child(6) > div")
"圆角"
# 定位圆角输入框
self.rounded_corners_locator = page.locator("div").filter(has_text=re.compile(r"^圆角$")).get_by_role("spinbutton")
"不透明度"
# 定位不透明度
self.opacity_locator = page.get_by_role("slider", name="slider between 0 and").locator("div").nth(1)
"""通用属性-文本属性"""
# 定位字号按钮
self.font_size_locator = page.get_by_label("文本").get_by_role("img").first
# 定位36号字体
self.font_size_36_locator = page.get_by_text("36", exact=True)
# 定位字体颜色按钮
self.font_color_button_locator = page.locator("div:nth-child(2) > .el-color-picker > .el-color-picker__trigger > .el-color-picker__color > .el-color-picker__color-inner").first
# 定位字体颜色,红色
self.red_font_color_button_locator = page.locator("div:nth-child(46) > div > .el-color-predefine > .el-color-predefine__colors > div:nth-child(4) > div")
# 定位对齐方式按钮
self.align_button_locator =page.get_by_label("文本").get_by_role("img").nth(1)
# 左对齐
self.left_aligned_locator = page.get_by_role("tooltip", name="  ").locator("i").nth(1)
# 居中对齐
self.center_aligned_locator = page.get_by_role("tooltip", name="  ").locator("i").nth(2)
# 右对齐
self.right_aligned_locator = page.get_by_role("tooltip", name="  ").locator("i").nth(3)
# 加粗
self.bold_locator =page.locator("//i[@title=\"粗体 Ctrl+B\"]")
# 斜体
self.italic = page.locator("//i[@title=\"斜体 Ctrl+I\"]")
# 下划线
self.underline = page.locator("//i[@title=\"下划线 Ctrl+U\"]")
"""顶部菜单栏"""
def set_screen_adaptation(self):
"""
设置画布为自适应
:return:
"""
self._click(pw_locator=self.zoom_locator)
self._click(pw_locator=self.screen_adaptation)
"""画面通用属性设置(位置、外观、文本)"""
def location_attribute_set_x_axis(self,set_value):
"""
位置属性:设置X
:param set_value:
"""
self._click(pw_locator=self.x_axis_locator) # 定位x轴输入框
self._fill(set_value,pw_locator=self.x_axis_locator) # 修改x
def location_attribute_set_y_axis(self, set_value):
"""
位置属性:设置Y
:param set_value:
"""
self._click(pw_locator=self.y_axis_locator) # 定位y轴输入框
self._fill(set_value, pw_locator=self.y_axis_locator) # 修改y
def location_attribute_set_rotation_angle(self, set_value):
"""
位置属性:设置旋转角度
:param set_value:
"""
self._click(pw_locator=self.rotation) # 定位旋转输入框
self._fill(set_value, pw_locator=self.rotation) # 修改旋转角度
def location_attribute_set_width(self, set_value):
"""
位置属性:设置W
:param set_value:
"""
self._click(pw_locator=self.w_locator) # 定位W轴输入框
self._fill(set_value, pw_locator=self.w_locator) # 修改W
# 位置属性修改(高)
def location_attribute_set_high(self, set_value):
"""
位置属性:设置H
:param set_value:
"""
self._click(pw_locator=self.h_locator) # 定位H输入框
self._fill(set_value, pw_locator=self.h_locator) # 修改H
def location_attribute_set_lock(self):
"""
位置属性:设置为锁定状态
"""
self._click(pw_locator=self.set_lock_locator) # 定位锁定按钮
def appearance_attribute_set_solid_color_background(self):
"""
外观属性:设置纯色背景,默认是红色
"""
self._click(pw_locator=self.solid_color_background_locator) #纯色背景
self._click(pw_locator=self.red_shortcut_button_locator) #设置红色
self._click(pw_locator=self.color_selection_window_ok_button_locator) #关闭颜色选择器弹窗
def appearance_attribute_set_gradient_color_background(self):
"""
外观属性:设置渐变色背景
"""
self._click(pw_locator=self.gradient_and_picture_button_locator)
self._click(pw_locator=self.gradient_button_locator) #点击渐变色按钮
self._click(pw_locator=self.gradient_button_locator) #再次点击,使其关闭弹窗
def appearance_attribute_set_image_background_stretch_type(self):
"""
外观属性:设置图片背景,且为拉伸类型
"""
self._click(pw_locator=self.gradient_and_picture_button_locator) #图片背景
self._click(pw_locator=self.add_image_locator)
self._click(pw_locator=self.image_locator)
self._click(pw_locator=self.image_select_button_locator)
self._click(pw_locator=self.image_stretch_type_locator) #设置为拉伸类型
def appearance_attribute_set_image_background_actual_type(self):
"""
外观属性:设置图片背景,且为实际大小类型
"""
self._click(pw_locator=self.gradient_and_picture_button_locator) #图片背景
self._click(pw_locator=self.add_image_locator)
self._click(pw_locator=self.image_locator)
self._click(pw_locator=self.image_select_button_locator)
self._click(pw_locator=self.image_actual_type_locator) #设置为实际大小类型
def appearance_attribute_set_image_background_cover_type(self):
"""
外观属性:设置图片背景,且为覆盖类型
"""
self._click(pw_locator=self.gradient_and_picture_button_locator) #图片背景
self._click(pw_locator=self.add_image_locator) #定位添加图片按钮
self._click(pw_locator=self.image_locator)
self._click(pw_locator=self.image_select_button_locator)
self._click(pw_locator=self.image_cover_type_locator) #设置为覆盖类型
def appearance_attribute_set_image_background_adapt_type(self):
"""
外观属性:设置图片背景,且为自适应类型
"""
self._click(pw_locator=self.gradient_and_picture_button_locator)
self._click(pw_locator=self.add_image_locator)
self._click(pw_locator=self.image_locator)
self._click(pw_locator=self.image_select_button_locator)
self._click(pw_locator=self.image_adapt_type_locator) #设置为覆盖类型
def appearance_attribute_set_straight_line_frame(self):
"""
外观属性:设置直线边框
:return:
"""
self._click(pw_locator=self.frame_type_button_locator)
self._click(pw_locator=self.straight_line_frame_locator) #直线
def appearance_attribute_set_dashed_line_frame(self):
"""
外观属性:设置虚线边框
:return:
"""
self._click(pw_locator=self.frame_type_button_locator)
self._click(pw_locator=self.dashed_line_frame_locator) #虚线
def appearance_attribute_set_scircular_dots_frame(self):
"""
外观属性:设置圆点边框
:return:
"""
self._click(pw_locator=self.frame_type_button_locator)
self._click(pw_locator=self.circular_dots_frame_locator) #圆点
def appearance_attribute_set_no_frame(self):
"""
外观属性:设置无边框
:return:
"""
self._click(pw_locator=self.frame_type_button_locator)
self._click(pw_locator=self.no_frame_locator) #无边框
def appearance_attribute_set_frame_width(self,value):
"""
外观属性:设置边框宽度
:param value: 边框宽度
:return:
"""
self._click(pw_locator=self.frame_width_locator)
self._fill(value,pw_locator=self.frame_width_locator) #边框宽度
def appearance_attribute_set_frame_colour(self):
"""
外观属性:设置边框颜色
:return:
"""
self._click(pw_locator=self.frame_colour_locator)
self._click(pw_locator=self.frame_red_colour_locator)
self._click(pw_locator=self.color_selection_window_ok_button_locator)
def appearance_attribute_set_external_shadow(self):
"""
外观属性:设置外阴影,颜色默认为红色
:return:
"""
self._click(pw_locator=self.external_shadow_button_locator) #点击外阴影按钮
self._click(pw_locator=self.external_shadow_ambiguity_locator) #设置模糊度
self._click(pw_locator=self.color_selector_for_external_shadows_locator) #定位颜色选择器按钮
self._click(pw_locator=self.color_of_external_shadow_locator) #设置外阴影颜色,红色
self._click(pw_locator=self.color_selection_window_ok_button_locator) #关闭外阴影弹窗
def appearance_attribute_set_internal_shadow(self):
"""
外观属性:设置内阴影,颜色默认为绿色
:return:
"""
self._click(pw_locator=self.internal_shadow_button_locator) #点击内阴影按钮
self._click(pw_locator=self.internal_shadow_ambiguity_locator) #设置模糊度
self._click(pw_locator=self.color_selector_for_internal_shadows_locator) #定位颜色选择器按钮
self._click(pw_locator=self.color_of_internal_shadow_locator) #设置内阴影颜色,绿色
self._click(pw_locator=self.color_selection_window_ok_button_locator) #关闭内阴影弹窗
def appearance_attribute_set_rounded_corners(self, value):
"""
外观属性:设置圆角弧度
:param value: 弧度值
:return:
"""
self._click(pw_locator=self.rounded_corners_locator)
self._fill(value, pw_locator=self.rounded_corners_locator)
def appearance_attribute_set_opacity(self):
"""
外观属性:设置不透明度
:return:
"""
self._click(pw_locator=self.opacity_locator)
def text_attribute_set_font_size(self):
"""
文本属性:设置字号
:return:
"""
self._click(pw_locator=self.font_size_locator) #点击字号
self._scroll_into_view_if_needed(pw_locator=self.font_size_36_locator) #滚动到36号字体位置
self._click(pw_locator=self.font_size_36_locator) #点击36号字体
def text_attribute_set_font_color(self):
"""
文本属性:设置字体颜色
:return:
"""
self._click(pw_locator=self.font_color_button_locator) #点击字体颜色按钮
self._click(pw_locator=self.red_font_color_button_locator) #设置颜色,红色
self._click(pw_locator=self.color_selection_window_ok_button_locator) #关闭弹窗
def text_attribute_set_left_aligned(self):
"""
文本属性:设置左对齐
:return:
"""
self._click(pw_locator=self.align_button_locator)
self._click(pw_locator=self.left_aligned_locator)
def text_attribute_set_center_aligned(self):
"""
文本属性:设置居中对齐
:return:
"""
self._click(pw_locator=self.align_button_locator)
self._click(pw_locator=self.center_aligned_locator)
def text_attribute_set_right_aligned(self):
"""
文本属性:设置右对齐
:return:
"""
self._click(pw_locator=self.align_button_locator)
self._click(pw_locator=self.right_aligned_locator)
def text_attribute_set_bold(self):
"""
文本属性:设置加粗
:return:
"""
self._click(pw_locator=self.bold_locator)
def text_attribute_set_italic(self):
"""
文本属性:设置斜体
:return:
"""
self._click(pw_locator=self.italic)
def text_attribute_set_underline(self):
"""
文本属性:设置下划线
:return:
"""
self._click(pw_locator=self.underline)
"""
画面工具栏操作
"""
# 已保存
def saved(self):
pass
# 预览
def preview(self):
pass
# 撤销
def undo(self):
pass
# 恢复
def redo(self):
pass
# 复制
def copy(self):
pass
# 粘贴
def paste(self):
pass
# 剪切
def cut(self):
pass
# 上一层
def previous_layer(self):
pass
# 下一层
def next_layer(self):
pass
# 顶层
def top_layer(self):
pass
# 底层
def bottom_layer(self):
pass
# 组合
def group(self):
pass
# 拆分
def split(self):
pass
# 左侧对齐
def left_align(self):
pass
# 居中对齐
def center_align(self):
pass
# 右侧对齐
def right_align(self):
pass
# 顶部对齐
def top_align(self):
pass
# 中部对齐
def middle_align(self):
pass
# 底部对齐
def bottom_align(self):
pass
# 水平
def horizontal(self):
pass
# 垂直
def vertical(self):
pass
# 等宽
def equal_width(self):
pass
# 等高
def equal_height(self):
pass
# 解锁
def unlock(self):
pass
# 显示
def display(self):
pass
# 组件箱
def component_box(self):
pass
# 钢笔
def pen(self):
pass
# 多语言
def multilingual(self):
pass
# 缩放
def zoom(self):
pass
# 工具栏收缩
def toolbar_collapse(self):
pass
# 工具栏展开
def toolbar_expand(self):
pass
"""
页面操作
"""
# 新建页面
def new_page(self, page_name):
# 点击新建页面按钮
self._click(pw_locator=self.cms_page_new_page_button_locator)
time.sleep(1)
# 输入页面名称
self.page.keyboard.type(page_name) # 通过键盘输入
self.page.keyboard.press("Enter") # 按下回车键
# 导出画面
def export_page(self):
# 点击待导出页面
self._click(pw_locator=self.cms_page_locator)
# 点击导出按钮
self._click(pw_locator=self.cms_page_export_button_locator)
# 导入画面
def import_page(self, import_file_name):
# 点击待导入页面
self._click(pw_locator=self.cms_page_locator)
self.page.on("dialog", lambda dialog: dialog.accept()) # 画面导入弹窗处理
# 点击导入按钮
with self.page.expect_file_chooser() as fc_info:
self._click(pw_locator=self.cms_page_import_button_locator)
file_chooser = fc_info.value
file_chooser.set_files(import_file_name)
# 基础组件_创建矩形框
def page_component_basic_rectangle(self):
# 点击展开组件菜单
self._click(pw_locator=self.cms_page_component_locator)
# 点击矩形组件
self._click(pw_locator=self.cms_page_component_rectangle_locator)
# 拖拉组件到画布
self._drag_and_drop_to_user_position(self.cms_page_component_rectangle_locator, Config.target_point_x, Config.target_point_y)
# Time : 2024/7/25 17:12
# Author : wangchao
# File : PageManagementPage.py
# Description :
import re
import time
from BasePage.BasePage import BasePage
from BuildInLibrary.BuildInLibrary import BuildInLibrary
from Config import Config
from Pages.PageManagementPage.PageManagementPage import PageManagementPage
class StaticTextPage(BasePage):
def __init__(self, page):
super().__init__(page)
# 创建静态文本
def create_static_text(self):
# 点击展开组件菜单
pmp = PageManagementPage(self.page)
self._click(pw_locator=pmp.cms_page_component_locator)
# 点击水平菜单组件
self._click(pw_locator=pmp.cms_page_component_static_text)
# 拖拉组件到画布
self._drag_and_drop_to_user_position(pmp.cms_page_component_static_text, Config.target_point_x,
Config.target_point_y)
time.sleep(0.5) # 等待0.5s加载属性栏
def set_xywh(self):
"""
拖出静态文本组件,设置x、y、w、h属性
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text() # 拖出静态文本组件
pmp.location_attribute_set_x_axis(300) # x
pmp.location_attribute_set_y_axis(400) # y
pmp.location_attribute_set_width(400) # w
pmp.location_attribute_set_high(200) # h
def set_rotation_angle(self):
"""
拖出静态文本组件,设置旋转角度
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text()
pmp.location_attribute_set_rotation_angle(45) # 旋转角度
def set_lock(self):
"""
拖出静态文本组件,设置锁定
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text()
pmp.location_attribute_set_lock()
def set_solid_color_background(self):
"""
拖出静态文本组件,设置纯色背景
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text()
pmp.appearance_attribute_set_solid_color_background()
def set_gradient_color_background(self):
"""
拖出静态文本组件,设置渐变色背景
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text()
pmp.appearance_attribute_set_gradient_color_background()
def set_stretch_type_image_background(self):
"""
拖出静态文本组件,设置图片背景,且为拉伸类型
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text()
pmp.location_attribute_set_width(400)
pmp.location_attribute_set_high(200)
time.sleep(0.5)
pmp.appearance_attribute_set_image_background_stretch_type() # 设置为拉伸类型
def set_adapt_type_image_background(self):
"""
拖出静态文本组件,设置图片背景,且为自适应类型
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text()
pmp.location_attribute_set_width(400)
pmp.location_attribute_set_high(200)
time.sleep(0.5)
pmp.appearance_attribute_set_image_background_adapt_type() # 设置为自适应类型
def set_cover_type_image_background(self):
"""
拖出静态文本组件,设置图片背景,且为覆盖类型
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text()
pmp.location_attribute_set_width(400)
pmp.location_attribute_set_high(200)
pmp.appearance_attribute_set_image_background_cover_type() # 设置为覆盖类型
def set_actual_type_image_background(self):
"""
拖出静态文本组件,设置图片背景,且为实际大小类型
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text()
pmp.location_attribute_set_width(400)
pmp.location_attribute_set_high(200)
time.sleep(0.5)
pmp.appearance_attribute_set_image_background_actual_type() # 设置为实际大小类型
def set_straight_line_frame(self):
"""
拖出静态文本组件,设置直线边框,宽度为5,颜色为红色
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text() # 拖出静态文本组件
pmp.location_attribute_set_width(400)
pmp.location_attribute_set_high(200)
time.sleep(0.5)
pmp.appearance_attribute_set_frame_width(5) # 边框宽度
pmp.appearance_attribute_set_straight_line_frame() # 直线
pmp.appearance_attribute_set_frame_colour() # 边框颜色
def set_dashed_line_frame(self):
"""
拖出静态文本组件,设置虚线边框,宽度为5,颜色为红色
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text() # 拖出静态文本组件
pmp.location_attribute_set_width(400)
pmp.location_attribute_set_high(200)
time.sleep(0.5)
pmp.appearance_attribute_set_frame_width(5) # 边框宽度
pmp.appearance_attribute_set_dashed_line_frame() # 虚线
pmp.appearance_attribute_set_frame_colour() # 边框颜色
def set_scircular_dots_frame(self):
"""
拖出静态文本组件,设置圆点边框,宽度为5,颜色为红色
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text() # 拖出静态文本组件
pmp.location_attribute_set_width(400)
pmp.location_attribute_set_high(200)
time.sleep(0.5)
pmp.appearance_attribute_set_frame_width(5) # 边框宽度
pmp.appearance_attribute_set_scircular_dots_frame() # 圆点
pmp.appearance_attribute_set_frame_colour() # 边框颜色
def set_no_frame(self):
"""
拖出静态文本组件,设置无边框,宽度为5,颜色为红色
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text() # 拖出静态文本组件
pmp.location_attribute_set_width(400)
pmp.location_attribute_set_high(200)
time.sleep(0.5)
pmp.appearance_attribute_set_frame_width(5) # 边框宽度
pmp.appearance_attribute_set_no_frame() # 圆点
pmp.appearance_attribute_set_frame_colour() # 边框颜色
def set_shadow(self):
"""
拖出静态文本组件,设置内外阴影
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text() # 拖出静态文本组件
pmp.location_attribute_set_width(400)
pmp.location_attribute_set_high(200)
time.sleep(0.5)
pmp.appearance_attribute_set_external_shadow()
time.sleep(1)
pmp.appearance_attribute_set_internal_shadow()
def set_rounded_corners(self):
"""
拖出静态文本组件,设置圆角
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text() # 拖出静态文本组件
pmp.location_attribute_set_width(400)
pmp.location_attribute_set_high(200)
time.sleep(0.5)
pmp.appearance_attribute_set_frame_width(5)
pmp.appearance_attribute_set_rounded_corners(50)
def set_opacity(self):
"""
拖出静态文本组件,设置不透明度
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text() # 拖出静态文本组件
pmp.appearance_attribute_set_solid_color_background()
time.sleep(0.5)
pmp.appearance_attribute_set_opacity()
def set_left_aligned(self):
"""
拖出静态文本组件,设置左对齐
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text() # 拖出静态文本组件
pmp.location_attribute_set_width(400)
pmp.location_attribute_set_high(200)
time.sleep(0.5)
pmp.text_attribute_set_left_aligned()
def set_center_aligned(self):
"""
拖出静态文本组件,设置左对齐
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text() # 拖出静态文本组件
pmp.location_attribute_set_width(400)
pmp.location_attribute_set_high(200)
time.sleep(0.5)
pmp.text_attribute_set_center_aligned()
def set_right_aligned(self):
"""
拖出静态文本组件,设置左对齐
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text() # 拖出静态文本组件
pmp.location_attribute_set_width(400)
pmp.location_attribute_set_high(200)
time.sleep(0.5)
pmp.text_attribute_set_right_aligned()
def set_font_style(self):
"""
拖出静态文本组件,设置左对齐
:return:
"""
pmp = PageManagementPage(self.page)
self.create_static_text() # 拖出静态文本组件
pmp.location_attribute_set_width(400)
pmp.location_attribute_set_high(200)
pmp.text_attribute_set_font_size()
time.sleep(0.5)
pmp.text_attribute_set_font_color()
time.sleep(0.5)
pmp.text_attribute_set_bold()
pmp.text_attribute_set_italic()
pmp.text_attribute_set_underline()
import os
import random
import re
import time
import allure
from playwright.sync_api import sync_playwright
from BasePage.BasePage import BasePage
from BuildInLibrary.BuildInLibrary import BuildInLibrary
class ProjectManagementPage(BasePage):
def __init__(self, page):
super().__init__(page)
"""工程管理页面元素定位器"""
# 创建空白工程
self.add_project_button_locator = page.locator(".el-tooltip__trigger > .iconfont").first
self.create_blank_project_locator = page.get_by_role("menuitem", name="新建空白工程")
self.project_name_input_locator = page.get_by_placeholder("请输入工程名称")
self.create_project_confirm_button_locator = page.get_by_role("button", name="确认")
# 新建文件夹
self.create_folder_button_locator = page.get_by_title("新建文件夹")
self.input_folder_name_locator = page.get_by_placeholder("请输入文件夹名称")
self.create_folder_confirm_button_locator = page.get_by_role("button", name="确认")
# 导入本地工程
self.add_project_button_locator = page.locator(".el-tooltip__trigger > .iconfont").first
self.import_local_project_locator = page.get_by_role("menuitem", name="导入本地工程")
# 选择工程(测试工程1)
self.select_project_locator = page.locator("tr:nth-child(2) > td > .vxe-cell > .vxe-cell--checkbox > span:nth-child(2)")
#数据备份
self.select_project_locator1 = page.get_by_role("row", name="222222 CMS 2024/06/26 13:45:48 ").locator("i") #name的值要根据实际值来调整
self.data_backup_locator = page.get_by_role("menuitem", name="数据备份")
self.select_backup_data_locator = page.get_by_role("dialog", name="数据备份").locator("label span").nth(1)
self.backup_data_confirm_button_locator = page.locator(".el-button.el-button--primary.cms-settings-button")
self.backup_file_name = page.get_by_placeholder("请输入备份文件名")
self.input_backup_file_name_confirm_button = page.locator("//*[@id=\"app\"]/div/div[1]/div[2]/div/div[3]/div/div/div/div[2]/div/div[2]/div/div/div/div[3]/div/div/button[2]")
self.data_backup_success_toast_locator = page.locator("//*[@id=\"message_1\"]")
#数据还原
self.data_recovery_locator = page.get_by_role("menuitem", name="数据恢复")
self.data_recovery_success_toast_locator = page.locator("//*[@id=\"message_1\"]")
#数据清除
self.data_clear_locator = page.get_by_role("menuitem", name="数据清除")
self.select_clear_data_locator = page.get_by_role("dialog", name="数据清除").locator("label span").nth(1)
self.clear_data_confirm_button_locator = page.locator("//*[@id=\"app\"]/div/div[1]/div[2]/div/div[3]/div/div/div/div[3]/div/div/button[2]")
self.data_clear_success_toast_locator = page.locator("//*[@id=\"message_1\"]")
#删除工程
self.delete_project_locator = page.locator("//*[@id=\"app\"]/div/div[1]/div[2]/div/div[1]/div[2]/div/div[2]/div[1]/div[1]/table/thead/tr/th[1]/div/span/label/span/span")
"""进入工程管理页面"""
def goto_project_management_page(self,url):
self._goto_url(url)
"""点击工程管理"""
def click_project_management(self):
self._click(pw_locator=self.add_project_button_locator)
"""新建空白工程"""
def create_blank_project(self, project_name):
self._click(pw_locator=self.add_project_button_locator)
self._click(pw_locator=self.create_blank_project_locator)
self._click(pw_locator=self.project_name_input_locator)
self._fill(project_name,pw_locator=self.project_name_input_locator)
self._click(pw_locator=self.create_project_confirm_button_locator)
"""导入本地工程"""
def import_local_project(self):
self._click(pw_locator=self.add_project_button_locator)
with self.page.expect_file_chooser() as fc_info:
self._click(pw_locator=self.import_local_project_locator) # 点击上传文件按钮
file_chooser = fc_info.value
bl = BuildInLibrary()
file_chooser.set_files(bl.get_path(f"UI自动化测试工程.cmsproj")) # 上传文件 ,或者上传多个文件file_chooser.set_files(['file1.txt', 'file2.txt'])
self.page.wait_for_load_state()
"""新建文件夹"""
def create_folder(self, folder_name):
self._click(pw_locator=self.create_folder_button_locator)
self.page.on("dialog", lambda dialog: dialog.accept())
self._fill(folder_name,pw_locator=self.input_folder_name_locator)
self._click(pw_locator=self.create_folder_confirm_button_locator)
"""选择工程"""
def select_project(self, project_name):
with self.page.expect_popup() as page1_info:
self.page.get_by_role("cell", name=project_name).get_by_text(project_name).click()
return page1_info.value
"""编辑工程_移动到"""
def move_to(self):
pass
"""编辑工程_重命名"""
def rename_project(self):
pass
"""编辑工程_创建副本"""
def create_copy(self):
pass
"""编辑工程_批量删除"""
def batch_delete_project(self):
self._click(pw_locator=self.delete_project_locator)
"""编辑工程_数据备份"""
def data_backup(self,backup_file_name):
self.page.on("dialog", lambda dialog: dialog.accept()) # 工程编辑弹窗处理
self._click(pw_locator=self.select_project_locator1)
self._click(pw_locator=self.data_backup_locator)
self.page.on("dialog", lambda dialog1: dialog1.accept()) # 选择备份数据弹窗处理
self._click(pw_locator=self.select_backup_data_locator)
self._click(pw_locator=self.backup_data_confirm_button_locator)
self.page.on("dialog", lambda dialog2: dialog2.accept()) # 输入备份文件名称弹窗处理
self._fill(backup_file_name,pw_locator=self.backup_file_name)
self._click(pw_locator=self.input_backup_file_name_confirm_button)
"""编辑工程_数据恢复"""
def data_recovery(self,backup_file_name):
self.page.on("dialog", lambda dialog: dialog.accept()) #功能编辑弹框处理
self._click(pw_locator=self.select_project_locator1)
with self.page.expect_file_chooser() as fc_info:
self._click(pw_locator=self.data_recovery_locator)
file_chooser = fc_info.value
file_chooser.set_files(backup_file_name)
"""编辑工程_数据清除"""
def data_clear(self):
self.page.on("dialog", lambda dialog: dialog.accept()) #工程编辑弹框处理
self._click(pw_locator=self.select_project_locator1)
self.page.on("dialog", lambda dialog1: dialog1.accept()) #选择清除数据弹框处理
self._click(pw_locator=self.data_clear_locator)
self._click(pw_locator=self.select_clear_data_locator)
self._click(pw_locator=self.clear_data_confirm_button_locator)
\ No newline at end of file
# Time : 2024/3/7 18:02
# Author : wangchao
# File : VariablePage.py
# Description : Page object of VariableManagementPage
import re
from time import sleep
import allure
from BasePage.BasePage import BasePage
from Config import Config
from Utils.logger_util import write_log
class VariableManagementPage(BasePage):
def __init__(self, page):
super().__init__(page) # 调用基类构造函数
"""元素定位器"""
# 开发版页面左侧菜单栏
self.variable_menu_locator = page.get_by_role("listitem").filter(has_text="变量").locator("i") # 变量菜单
# 全局导入变量
self.import_variable_locator = page.locator("//*[@id=\"app\"]/div/div[1]/div[2]/div/div/div/div[2]/button[6]")
self.import_mode_choice_up_down_menu_locator = page.get_by_role("dialog", name="导入").locator("i").first
self.choice_import_mode_locator = page.get_by_text("覆盖导入")
self.choice_import_mode_confirm_locator = page.get_by_role("button", name="确定")
self.import_variable_success_toast_locator = page.locator("//*[@id=\"message_1\"]/p")
# 全局导出变量
self.export_variable_locator = page.locator("//*[@id=\"app\"]/div/div[1]/div[2]/div/div/div/div[2]/button[5]")
self.export_variable_success_toast_locator = page.locator("//*[@id=\"message_1\"]/p")
# IO变量导入
self.IO_variable_group_check_locator = page.locator(".el-tooltip__trigger > .iconfont").first
self.IO_variable_group_import_locator = page.get_by_role("menuitem", name="导入")
# IO变量导出
self.IO_variable_group_export_locator = page.get_by_role("menuitem", name="导出")
# 内部变量导入
self.click_inner_channel_tab_locator = page.get_by_role("button", name="内部通道")
self.click_inner_variable_channel_locator = page.locator("#app").get_by_text("内部变量") # 点击内部通道下的内部变量
self.inner_variable_edit_menu_locator = page.locator(".el-tooltip__trigger > .iconfont").first # 点击“内部变量”编辑按钮
self.inner_variable_group_import_locator = page.get_by_role("menuitem", name="导入")
self.inner_variable_import_mode_choice_up_down_menu_locator = page.get_by_role("dialog", name="导入").locator("i").first
self.inner_variable_import_mode_locator = page.get_by_text("覆盖导入")
self.inner_variable_import_mode_confirm_locator = page.get_by_role("button", name="确定")
self.inner_variable_import_success_toast_locator = page.locator("//*[@id=\"message_1\"]/p")
# 内部变量导出
self.inner_variable_group_export_locator = page.get_by_role("menuitem", name="导出")
self.inner_variable_export_success_toast_locator = page.locator("//*[@id=\"message_1\"]/p")
# 批量添加变量(IO变量)
self.check_io_variable_locator = page.locator(".vxe-cell > .vxe-cell--checkbox > span:nth-child(2)").first # 选择IO通道变量列表第一个变量
self.add_io_variables_in_batches_button_locator = page.locator("//button[@class='iconfont icon-piliangtianjia']") # 点击批量添加变量按钮
# 变量批量处理
self.click_io_variable_group_locator = page.get_by_title("IO_Channel_Modbus_TCP").get_by_text("IO_Channel_Modbus_TCP") # 点击IO通道变量组
self.check_for_all_io_variables_locator = page.locator("//span[@class='vxe-cell--title']//span[@class='vxe-checkbox--icon vxe-checkbox--unchecked-icon']") # 全选变量
self.deal_with_variable_in_batches_button_locator = page.locator("//button[@class='iconfont icon-bianliangpiliangchuli']") # 点击批量处理变量按钮
self.variable_read_and_write_mode_up_and_down_button_locator = page.locator("div").filter(has_text=re.compile(r"^读写访问:$")).locator("i") # 变量读写方式下拉框
self.check_read_and_write_mode_of_variable_locator = page.get_by_role("tooltip", name="请选择 读写 只读 只写").get_by_text("读写") # 选择读写
self.batch_modification_variable_confirm_button_locator = page.get_by_role("button", name="确定") # 批量修改页面确定
self.batch_modification_variable_save_changes_locator = page.get_by_role("button", name="保存") # 保存修改
self.batch_modification_variable_success_toast_locator = page.locator("//*[@id=\"message_1\"]/p") # 保存成功toast
"""变量全局导入"""
@allure.step("变量全局导入")
def import_global_variable_group(self, import_file_name):
# 点击变量菜单
self._click(pw_locator=self.variable_menu_locator)
self.page.on("dialog", lambda dialog: dialog.accept()) # 弹窗处理
# 点击变量全局导入按钮
self._click(pw_locator=self.import_variable_locator)
# 导入方式下拉框
self._click(pw_locator=self.import_mode_choice_up_down_menu_locator)
# 点击覆盖导入
self._click(pw_locator=self.choice_import_mode_locator)
# 点击确定按钮并选择文件
with self.page.expect_file_chooser() as fc_info:
self._click(pw_locator=self.choice_import_mode_confirm_locator)
file_chooser = fc_info.value
file_chooser.set_files(import_file_name)
"""变量全局导出"""
@allure.step("变量全局导出")
def export_global_variable_group(self):
# 点击变量菜单
self._click(pw_locator=self.variable_menu_locator)
# 点击变量全局导出
self._click(pw_locator=self.export_variable_locator)
"""IO变量导入"""
@allure.step("IO变量导入")
def import_io_variable_group(self, import_file_name):
# 点击变量菜单
self._click(pw_locator=self.variable_menu_locator)
# 选择IO变量组
self.page.on("dialog", lambda dialog1: dialog1.accept()) # IO变量组编辑弹窗处理
self._click(pw_locator=self.IO_variable_group_check_locator)
# 点击导入
self.page.on("dialog", lambda dialog2: dialog2.accept()) # 导入方式选择弹窗处理
self._click(pw_locator=self.IO_variable_group_import_locator)
# 导入方式下拉框
self._click(pw_locator=self.import_mode_choice_up_down_menu_locator)
# 点击覆盖导入
self._click(pw_locator=self.choice_import_mode_locator)
# 点击确定按钮并选择文件
with self.page.expect_file_chooser() as fc_info:
self._click(pw_locator=self.choice_import_mode_confirm_locator)
file_chooser = fc_info.value
file_chooser.set_files(import_file_name)
"""IO变量导出"""
@allure.step("IO变量导出")
def export_io_variable_group(self):
# 点击变量菜单
self._click(pw_locator=self.variable_menu_locator)
# 选择IO变量组
self._click(pw_locator=self.IO_variable_group_check_locator)
self.page.on("dialog", lambda dialog: dialog.accept()) # 弹窗处理
# 点击导出
self._click(pw_locator=self.IO_variable_group_export_locator)
"""内部变量导入"""
@allure.step("内部变量导入")
def import_internal_variable_group(self, import_file_name):
# 点击内部通道tab
self._click(pw_locator=self.click_inner_channel_tab_locator)
self.page.on("dialog", lambda dialog: dialog.accept()) # 内部变量组编辑菜单弹窗处理
# 点击内部变量组编辑菜单
self._click(pw_locator=self.inner_variable_edit_menu_locator)
# 点击导入按钮
self._click(pw_locator=self.inner_variable_group_import_locator)
# 导入方式下拉框
self._click(pw_locator=self.inner_variable_import_mode_choice_up_down_menu_locator)
# 点击覆盖导入
self._click(pw_locator=self.inner_variable_import_mode_locator)
# 点击确定按钮并选择文件
with self.page.expect_file_chooser() as fc_info:
self._click(pw_locator=self.inner_variable_import_mode_confirm_locator)
file_chooser = fc_info.value
file_chooser.set_files(import_file_name)
"""内部变量导出"""
@allure.step("内部变量导出")
def export_internal_variable_group(self):
# 点击内部通道tab
self._click(pw_locator=self.click_inner_channel_tab_locator)
self.page.on("dialog", lambda dialog: dialog.accept()) # 内部变量组编辑菜单弹窗处理
# 点击内部变量组编辑菜单
self._click(pw_locator=self.inner_variable_edit_menu_locator)
# 点击导出
self._click(pw_locator=self.inner_variable_group_export_locator)
"""批量处理变量"""
@allure.step("批量处理变量")
def batch_handle_variable(self):
# 点击IO通道变量组
self._click(pw_locator=self.click_io_variable_group_locator)
# 等待2S
self.page.wait_for_timeout(2000)
# 全选IO变量
self._click(pw_locator=self.check_for_all_io_variables_locator)
self.page.on("dialog", lambda dialog: dialog.accept()) # 变量批量处理弹窗处理
# 点击批量处理变量按钮
self._click(pw_locator=self.deal_with_variable_in_batches_button_locator)
# 变量读写方式下拉框
self._click(pw_locator=self.variable_read_and_write_mode_up_and_down_button_locator)
# 选择读写
self._click(pw_locator=self.check_read_and_write_mode_of_variable_locator)
# 批量修改页面确定
self._click(pw_locator=self.batch_modification_variable_confirm_button_locator)
# 保存修改
self._click(pw_locator=self.batch_modification_variable_save_changes_locator)
# CMS2.0UI自动化测试
#### 介绍
CMS2.0_UI_AUTO_TEST,CMS2.0基座UI自动化测试框架。
#### 软件架构
python语言,pytest&playwright&allure测试框架,pom页面对象模型封装。
#### 安装教程
1. 安装python: python 3.10
2. 删除工程中venv文件夹
3. 创建虚拟环境:python -m venv venv
4. 激活虚拟环境:.\venv\Scripts\activate
5. 安装工程依赖包:pip install -r requirements.txt
6. 安装playwright浏览器内核: playwright install
#### 框架工程目录说明
1. 执行runner.py运行,会自动收集测试用例执行结果,并生成allure报告
2. BasePage:封装playwright页面基础操作方法
3. BuildInLibrary:环境变量存放文件夹,可进行用例参数关联
4. Common:存放公共方法抽离文件夹
5. Config:配置文件存放文件夹
6. Logs:存放断言失败及fixture方法执行的日志
7. Pages:存放页面对象文件
8. ProjectData:存放工程文件及变量导入文件
9. TestCases:存放测试用例
10. TestDatas:存放测试数据
11. TestReport:存放allure测试报告
12. Utils:存放工具类
13. runner.py:运行入口文件
14. ImageComparison:存放用例页面截图及预期要对比的图片
#### 测试数据文件(TestDatas/XXData/TestXXX.yaml)参数化使用说明
1. 如要参数化测试数据文件中的某个字段,使用{{}}格式进行包裹,如:'{{$username}}'
#### 框架使用说明
1. 执行测试用例前,框架会自动创建一个空白工程并初始化,执行测试用例后,框架会自动删除工程
2. 对于无法通过playwright提供的断言方式进行断言的场景,框架提供图像对比的功能进行断言
3. 页面通用操作方法,建议封装到BasePage类进行调用
### testcase 使用说明
1. 图片比对:调用[BasePage.py]下的compare_images(参数1,参数2,参数3)函数,3个参数分别为实际图名称、预期图名称、平台类型,默认都是png类型;参数3默认是开发版,可不传,若指定运行版需传入vision关键字
# Time : 2024/7/30 09:13
# Author : wangchao
# File : mode_PageManagement.py
# Description : 画面管理测试类
# -*- coding: utf-8 -*-
import os
import time
import allure
import pytest
from playwright.sync_api import expect
from BasePage.BasePage import BasePage
from Common.AllurePretty import PrettyAllure
from Common.ImageComparison import ImageComparisonTool
from Common.ScreenCapture import ScreenCapture
from Config import Config
from Pages.PageManagementPage.PageManagementPage import PageManagementPage
from Utils.ReadYaml import ReadYaml
@allure.epic("CMS2.0UI自动化测试")
@allure.feature("页面管理")
class TestPageManagement:
"""菜单栏测试用例"""
pass
# Time : 2024/8/22 09:13
# Author : lxl
# File : Test_StaticText.py
# Description : 水平菜单组件测试类
# -*- coding: utf-8 -*-
import os
import time
import allure
import pytest
from BasePage.BasePage import BasePage
from Pages.PageManagementPage.PageManagementPage import PageManagementPage
from Pages.PageManagementPage.StaticTextPage import StaticTextPage
from Config import Config
@allure.epic("CMS2.0UI自动化测试")
@allure.feature("静态文本组件")
class Test_StaticText:
# @allure.story("静态文本-创建静态文本组件")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name",[("静态文本","静态文本_创建静态文本_实际图", "静态文本_创建静态文本_预期图")])
# def test_StaticText(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.create_static_text()
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
#
# @allure.story("静态文本-修改x、y、w、h属性")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name",[("静态文本","静态文本_修改xywh属性_实际图", "静态文本_修改xywh属性_预期图")])
# def test_StaticText_set_xywh_axis(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.set_xywh()
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
#
#
# @allure.story("静态文本-修改旋转角度")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name",[("静态文本","静态文本_修改旋转角度_实际图", "静态文本_修改旋转角度_预期图")])
# def test_StaticText_set_rotation_angle(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.set_rotation_angle()
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
#
# @allure.story("静态文本-设置锁定")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name",[("静态文本","静态文本_设置锁定_实际图", "静态文本_设置锁定_预期图")])
# def test_StaticText_set_lock(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.set_lock()
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
#
# @allure.story("静态文本-设置纯色背景")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name",[("静态文本","静态文本_设置纯色背景_实际图", "静态文本_设置纯色背景_预期图")])
# def test_StaticText_set_solid_color_background(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.set_solid_color_background()
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
#
# @allure.story("静态文本-设置渐变色背景")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name", [("静态文本","静态文本_设置渐变色背景_实际图", "静态文本_设置渐变色背景_预期图")])
# def test_StaticText_set_gradient_color_background(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.set_gradient_color_background()
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
#
# @allure.story("静态文本-设置图片背景,且为拉伸类型")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name",[("静态文本", "静态文本_设置图片背景_拉伸类型_实际图", "静态文本_设置图片背景_拉伸类型_预期图")])
# def test_StaticText_set_stretch_type_image_background(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.set_stretch_type_image_background()
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
#
# @allure.story("静态文本-设置图片背景,且为自适应类型")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name",
# [("静态文本", "静态文本_设置图片背景_自适应类型_实际图", "静态文本_设置图片背景_自适应类型_预期图")])
# def test_StaticText_set_adapt_type_image_background(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.set_adapt_type_image_background()
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
#
# @allure.story("静态文本-设置图片背景,且为覆盖类型")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name",[("静态文本", "静态文本_设置图片背景_覆盖类型_实际图", "静态文本_设置图片背景_覆盖类型_预期图")])
# def test_StaticText_set_cover_type_image_background(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.set_cover_type_image_background()
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
#
# @allure.story("静态文本-设置图片背景,且为实际大小类型")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name",[("静态文本", "静态文本_设置图片背景_实际大小类型_实际图", "静态文本_设置图片背景_实际大小类型_预期图")])
# def test_StaticText_set_actual_type_image_background(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.set_actual_type_image_background()
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
#
# @allure.story("静态文本-设置直线边框")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name", [("静态文本", "静态文本_设置直线边框_实际图", "静态文本_设置直线边框_预期图")])
# def test_StaticText_set_straight_line_frame(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.set_straight_line_frame() #直线
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
#
# @allure.story("静态文本-设置虚线边框")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name",[("静态文本","静态文本_设置虚线边框_实际图", "静态文本_设置虚线边框_预期图")])
# def test_StaticText_set_dashed_line_frame(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.set_dashed_line_frame() #虚线
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
#
# @allure.story("静态文本-设置圆点边框")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name",[("静态文本","静态文本_设置圆点边框_实际图", "静态文本_设置圆点边框_预期图")])
# def test_StaticText_set_scircular_dots_frame(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.set_scircular_dots_frame() #圆点
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
#
# @allure.story("静态文本-设置无边框")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name",[("静态文本","静态文本_设置无边框_实际图", "静态文本_设置无边框_预期图")])
# def test_StaticText_set_no_frame(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.set_no_frame() #无边框
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
#
# @allure.story("静态文本-设置内外阴影")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name",[("静态文本","静态文本_设置内外阴影_实际图", "静态文本_设置内外阴影_预期图")])
# def test_StaticText_set_shadow(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.set_shadow() #设置内外阴影
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
#
# @allure.story("静态文本-设置圆角")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name",[("静态文本","静态文本_设置圆角_实际图", "静态文本_设置圆角_预期图")])
# def test_StaticText_set_rounded_corners(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.set_rounded_corners()
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
#
# @allure.story("静态文本-设置不透明度")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name",[("静态文本","静态文本_设置不透明度_实际图", "静态文本_设置不透明度_预期图")])
# def test_StaticText_set_opacity(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.set_opacity()
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
#
# @allure.story("静态文本-设置不透明度")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name",[("静态文本","静态文本_设置不透明度_实际图", "静态文本_设置不透明度_预期图")])
# def test_StaticText_set_opacity(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.set_opacity()
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
#
# @allure.story("静态文本-设置左对齐")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name",[("静态文本","静态文本_设置左对齐_实际图", "静态文本_设置左对齐_预期图")])
# def test_StaticText_set_left_aligned(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.set_left_aligned()
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
#
# @allure.story("静态文本-设置居中对齐")
# @pytest.mark.parametrize("filename,actual_img_name,expect_img_name",[("静态文本","静态文本_设置居中对齐_实际图", "静态文本_设置居中对齐_预期图")])
# def test_StaticText_set_center_aligned(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
# bp = BasePage(page)
# bp._goto_url("画面")
# stp = StaticTextPage(page)
# stp.set_center_aligned()
# time.sleep(1)
# result = bp.compare_images(filename, actual_img_name, expect_img_name)
# bp.allure_reports(filename, actual_img_name, expect_img_name)
# assert result, f"页面截图与预期截图不一致"
@allure.story("静态文本-设置右对齐")
@pytest.mark.parametrize("filename,actual_img_name,expect_img_name",[("静态文本","静态文本_设置右对齐_实际图", "静态文本_设置右对齐_预期图")])
def test_StaticText_set_right_aligned(self, page, create_and_destroy_page, filename, actual_img_name, expect_img_name):
bp = BasePage(page)
bp._goto_url("画面")
stp = StaticTextPage(page)
stp.set_right_aligned()
time.sleep(1)
result = bp.compare_images(filename, actual_img_name, expect_img_name)
bp.allure_reports(filename, actual_img_name, expect_img_name)
assert result, f"页面截图与预期截图不一致"
import builtins
import time
from playwright.sync_api import sync_playwright
import re
from PIL import Image, ImageChops
from Pages.PageManagementPage.PageManagementPage import PageManagementPage
def compare_images(image_path1, image_path2, threshold=50):
"""
比较两个图片是否相似。
:param image_path1: 第一个图片的路径
:param image_path2: 第二个图片的路径
:param threshold: 差异像素数的阈值
:return: 如果图片相似则返回 True,否则返回 False
"""
img1 = Image.open(image_path1)
img2 = Image.open(image_path2)
# 确保两个图片具有相同的模式和大小
if img1.mode != img2.mode or img1.size != img2.size:
return False
# 计算差异
diff = ImageChops.difference(img1, img2)
# 将差异图片转换为灰度,并获取差异像素的总数
diff_gray = diff.convert('L')
squared_pixels = [i ** 2 for i in list(diff_gray.getdata())]
rmse = (sum(squared_pixels) / len(squared_pixels)) ** 0.5
# 根据均方根误差(RMSE)和阈值判断图片是否相似
return rmse < threshold
def run(playwright):
browser = playwright.chromium.launch(headless=False) # 启动无头模式或带有界面的浏览器
page = browser.new_page(viewport={'width': 1850, 'height': 1000 })
page.goto('http://127.0.0.1:18000/#/editing/project/66/page') # 导航到目标网页
time.sleep(1)
page.locator("//*[@id=\"app\"]/div/div[1]/div[2]/div/main/div[2]/main/div[3]/ul/li[1]/i").click()
time.sleep(1)
page.get_by_label("基础").locator("div").filter(has_text=re.compile(r"^静态文本$")).click()
time.sleep(1)
#按住鼠标左键
page.mouse.down(button='left')
#移动坐标
drop_x, drop_y = 910, 560
time.sleep(1)
# 拖动到目标位置
page.mouse.move(drop_x, drop_y)
time.sleep(1)
# 释放鼠标左键
page.mouse.up(button='left')
time.sleep(1)
# 设置字体背景样式
# page.get_by_role("checkbox", name="粗体 Ctrl+B").click()
# page.locator("//i[@title=\"粗体 Ctrl+B\"]").click()
# time.sleep(1)
## 斜体
# page.locator("//i[@title=\"斜体 Ctrl+I\"]").click()
# time.sleep(1)
## 下划线
# page.locator("//i[@title=\"下划线 Ctrl+U\"]").click()
# time.sleep(1)
# page.w_locator = page.locator("div").filter(has_text=re.compile(r"^W$")).get_by_role("spinbutton").click()
# page.locator("div").filter(has_text=re.compile(r"^W$")).get_by_role("spinbutton").fill('400')
# # H
# page.h_locator = page.locator("div").filter(has_text=re.compile(r"^H$")).get_by_role("spinbutton").click()
# page.locator("div").filter(has_text=re.compile(r"^H$")).get_by_role("spinbutton").fill('200')
# time.sleep(1)
# page.get_by_title("粗体 Ctrl+B").click()
# page.get_by_label("外观").locator("div").filter(has_text=re.compile(r"^背景$")).locator("span").nth(1).click()
# page.locator(
# "div:nth-child(18) > div > .el-color-predefine > .el-color-predefine__colors > div:nth-child(4) > div").click()
# page.get_by_role("button", name="OK").click()
## 设置纯色背景
# page.get_by_label("外观").locator("div").filter(has_text=re.compile(r"^背景$")).locator("span").nth(1).click()
# page.locator(
# "div:nth-child(18) > div > .el-color-predefine > .el-color-predefine__colors > div:nth-child(4) > div").click()
# page.get_by_role("button", name="OK").click()
#设置渐变色背景
# page.get_by_label("外观").locator("use").click()
# page.get_by_label("外观").get_by_title("线性渐变").click()
# time.sleep(2)
# page.locator("div").filter(has_text=re.compile(r"^W$")).get_by_role("spinbutton").fill('300')
# # 定位H位置
# page.locator("div").filter(has_text=re.compile(r"^H$")).get_by_role("spinbutton").fill('200')
# page.pause()
# #设置背景图
# page.get_by_label("外观").locator("use").click()
# # page.get_by_label("外观").locator("use").click()
# # page.get_by_label("外观").get_by_title("背景图").click()
# # // span[text() = "添加图片"]
# # page.get_by_text("添加图片").click()
# page.locator("//span[text()=\"添加图片\"]").nth(1).click()
# page.locator("//div[@class='component-gallery-material-list']//div[1]//div[1]//img[1]").click()
# page.get_by_role("textbox", name="Select").click()
# page.get_by_text("拉伸").nth(1).click()
# time.sleep(2)
# # 边框
# page.pause()
# page.locator("div").filter(has_text=re.compile(r"^边框$")).get_by_role("spinbutton").click()
# page.locator("div").filter(has_text=re.compile(r"^边框$")).get_by_role("spinbutton").fill("5")
# page.get_by_placeholder(" ").click()
# # page.get_by_text("直线").click()
# # page.get_by_text("圆点").click()
# page.locator("div").filter(has_text=re.compile(r"^虚线$")).locator("span").click()
# page.locator(
# "div:nth-child(3) > .el-color-picker > .el-color-picker__trigger > .el-color-picker__color > .el-color-picker__color-inner").first.click()
# page.locator(
# "div:nth-child(30) > div > .el-color-predefine > .el-color-predefine__colors > div:nth-child(4) > div").click()
# # page.get_by_text("无", exact=True).click()
# time.sleep(2)
# 阴影
# page.pause()
# page.get_by_title("外阴影").click()
# # page.locator("div:nth-child(3) > .bar > .el-slider > .el-slider__runway > .el-slider__button-wrapper > .el-slider__button").first.click()
# page.get_by_role("slider", name="slider between 0 and 50").locator("div").first.click()
# page.get_by_role("tooltip", name="水平: 0 0 垂直: 0 0 模糊: 25 0 大小:").locator("span").nth(1).click()
# page.locator("div:nth-child(37) > div > .el-color-predefine > .el-color-predefine__colors > div:nth-child(4) > div").click()
# page.get_by_role("button", name="OK").click()
# time.sleep(1)
# page.get_by_title("内阴影(可能被内部内容遮挡)").click()
# page.get_by_role("slider", name="slider between 0 and 50").locator("div").first.click()
# page.get_by_role("tooltip", name="水平: 0 0 垂直: 0 0 模糊: 25 0 大小:").locator("span").nth(1).click()
# page.locator("div:nth-child(43) > div > .el-color-predefine > .el-color-predefine__colors > div:nth-child(6) > div").click()
# page.get_by_role("button", name="OK").click()
#圆角
# page.locator("div").filter(has_text=re.compile(r"^圆角$")).get_by_role("spinbutton").click()
# page.locator("div").filter(has_text=re.compile(r"^圆角$")).get_by_role("spinbutton").fill("50")
# time.sleep(1)
# page.pause()
# # 不透明度
# page.get_by_role("slider", name="slider between 0 and").locator("div").nth(1).click()
# time.sleep(1)
# 字号
page.pause()
# page.get_by_label("文本").get_by_role("img").first.click()
# page.get_by_text("36", exact=True).scroll_into_view_if_needed()
# page.get_by_text("36", exact=True).click()
# #字体颜色
# page.locator("div:nth-child(2) > .el-color-picker > .el-color-picker__trigger > .el-color-picker__color > .el-color-picker__color-inner").first.click()
# page.locator("div:nth-child(46) > div > .el-color-predefine > .el-color-predefine__colors > div:nth-child(4) > div").click()
# time.sleep(1)
# page.get_by_label("文本").get_by_role("img").nth(1).click()
# page.get_by_role("tooltip", name="  ").locator("i").nth(1).click()
# time.sleep(2)
# page.get_by_label("文本").get_by_role("img").nth(1).click()
# page.get_by_role("tooltip", name="  ").locator("i").nth(2).click()
# time.sleep(2)
# page.get_by_label("文本").get_by_role("img").nth(1).click()
# page.get_by_role("tooltip", name="  ").locator("i").nth(3).click()
# time.sleep(2)
# #全屏截图
# page.screenshot(path='full_screenshot.png')
# #拖到画布指定位置
# region = (340, 290, 1630, 1030)
# #指定区域截图
# with Image.open('full_screenshot.png') as img:
# cropped_img = img.crop(region) # region 应该是一个四元组 (left, upper, right, lower)
# cropped_img.save('cropped_screenshot.png')
# #调用图片比对方法
# is_similar = compare_images('cropped_screenshot.png', 'expected_image.png')
#
# # 输出结果
# if is_similar:
# print("图片相似")
# else:
# print("图片不相似")
browser.close()
with sync_playwright() as playwright:
run(playwright)
\ No newline at end of file
# Time : 2024/7/23 18:02
# Author : wangchao
# File : Test_HistoryLibraryManagement.py
# Description : 历史库管理测试类
import os.path
import allure
import pytest
from playwright.sync_api import expect
from BasePage.BasePage import BasePage
from Common.AllurePretty import PrettyAllure
from Config import Config
from Pages.HistoryLibraryPage.HistoryLibraryManagement import HistoryLibraryManagement
from Utils.ReadYaml import ReadYaml
@allure.epic("CMS2.0UI自动化测试")
@allure.feature("历史库管理")
class TestHistoryLibraryManagement:
@allure.story("历史库数据备份")
@pytest.mark.run(order=1)
@PrettyAllure.PrettyAllureCaseWarpper
@pytest.mark.parametrize("CaseData", ReadYaml(os.path.join(Config.test_datas_dir, "HistoryLibraryManagementData/TestHistoryLibraryBackup.yaml")).read())
def test_history_library_backup(self, page, CaseData: dict):
bp = BasePage(page)
hlp = HistoryLibraryManagement(page)
bp._goto_url_history_management()
hlp.history_library_backup()
expect(hlp.history_library_backup_success_toast_locator).to_have_text("数据备份成功") #断言备份成功
@allure.story("历史库数据清除")
@pytest.mark.run(order=1)
@PrettyAllure.PrettyAllureCaseWarpper
@pytest.mark.parametrize("CaseData", ReadYaml(os.path.join(Config.test_datas_dir, "HistoryLibraryManagementData/TestHistoryLibraryClear.yaml")).read())
def test_history_library_clear(self, page, CaseData: dict):
bp = BasePage(page)
hlp = HistoryLibraryManagement(page)
bp._goto_url_history_management()
hlp.history_library_clear()
expect(hlp.history_library_clear_success_toast_locator).to_have_text("数据清除成功") #断言清除成功
import os
import time
import allure
import pytest
import re
from playwright.sync_api import expect
from BasePage.BasePage import BasePage
from BuildInLibrary.BuildInLibrary import BuildInLibrary
from Common.AllurePretty import PrettyAllure
from Pages.ProjectManagementPage.ProjectManagementPage import ProjectManagementPage
from Config import Config
from Utils.ReadYaml import ReadYaml
from Utils.VisualRegression import visual_regression
@allure.epic("CMS2.0UI自动化测试")
@allure.feature("工程管理")
class TestProjectManagement:
@allure.story("新建空白工程")
@pytest.mark.run(order=1)
@PrettyAllure.PrettyAllureCaseWarpper
@pytest.mark.parametrize("CaseData", ReadYaml(os.path.join(Config.test_datas_dir, "ProjectManagementData/TestCreateProjectData.yaml")).read())
def test_create_project(self, page, CaseData: dict):
bp = BasePage(page)
pmp = ProjectManagementPage(page)
bp._goto_url_project_management()
pmp.click_project_management()
pmp.create_blank_project(CaseData["参数化值-工程名"])
expect(pmp.page.get_by_role("cell", name=CaseData["参数化值-工程名"]).locator("span")).to_have_text(CaseData["参数化值-工程名"])
@allure.story("新建工程文件夹")
@pytest.mark.run(order=1)
@PrettyAllure.PrettyAllureCaseWarpper
@pytest.mark.parametrize("CaseData", ReadYaml(os.path.join(Config.test_datas_dir, "ProjectManagementData/TestCreateProjectFolderData.yaml")).read())
def test_create_folder(self, page, CaseData: dict):
bp = BasePage(page)
pmp = ProjectManagementPage(page)
bp._goto_url_project_management()
pmp.create_folder(CaseData["参数化值-文件夹名"])
expect(pmp.page.get_by_role("cell", name=CaseData["参数化值-文件夹名"]).locator("span")).to_have_text(CaseData["参数化值-文件夹名"])
@allure.story("导入本地工程")
@pytest.mark.run(order=1)
@PrettyAllure.PrettyAllureCaseWarpper
@pytest.mark.parametrize("CaseData", ReadYaml(os.path.join(Config.test_datas_dir, "ProjectManagementData/TestImportLocalProjectData.yaml")).read())
def test_import_local_project(self, page, CaseData: dict):
bp = BasePage(page)
pmp = ProjectManagementPage(page)
bp._goto_url_project_management()
pmp.import_local_project()
@allure.story("编辑工程_数据备份")
@pytest.mark.run(order=1)
@PrettyAllure.PrettyAllureCaseWarpper
@pytest.mark.parametrize("CaseData", ReadYaml(os.path.join(Config.test_datas_dir, "ProjectManagementData/TestDataBackupData.yaml")).read())
def test_data_backup(self, page, CaseData: dict):
bp = BasePage(page)
pmp = ProjectManagementPage(page)
bp._goto_url_project_management()
pmp.data_backup(CaseData["参数化值-工程数据备份文件名"])
expect(pmp.data_backup_success_toast_locator).to_have_text("备份成功")#断言备份成功
@allure.story("编辑工程_数据恢复")
@pytest.mark.run(order=1)
@PrettyAllure.PrettyAllureCaseWarpper
@pytest.mark.parametrize("CaseData", ReadYaml(os.path.join(Config.test_datas_dir, "ProjectManagementData/TestDataRecoveryData.yaml")).read())
def test_data_recovery(self, page, CaseData: dict):
bp = BasePage(page)
pmp = ProjectManagementPage(page)
bp._goto_url_project_management()
pmp.data_recovery(CaseData["参数化值-工程数据备份文件名"])
expect(pmp.data_recovery_success_toast_locator).to_have_text("数据恢复成功")
@allure.story("编辑工程_数据清除")
@pytest.mark.run(order=1)
@PrettyAllure.PrettyAllureCaseWarpper
@pytest.mark.parametrize("CaseData", ReadYaml(os.path.join(Config.test_datas_dir, "ProjectManagementData/TestDataClearData.yaml")).read())
def test_data_clear(self, page, CaseData: dict):
bp = BasePage(page)
pmp = ProjectManagementPage(page)
bp._goto_url_project_management()
pmp.data_clear()
expect(pmp.data_clear_success_toast_locator).to_have_text("清除成功")
@allure.story("编辑工程_删除(批量删除)")
@pytest.mark.run(order=1)
@PrettyAllure.PrettyAllureCaseWarpper
@pytest.mark.parametrize("CaseData", ReadYaml(os.path.join(Config.test_datas_dir, "ProjectManagementData/TestBatchDeleteProjectData.yaml")).read())
def test_batch_delete_project(self, page, CaseData: dict):
bp = BasePage(page)
pmp = ProjectManagementPage(page)
bp._goto_url_project_management()
pmp.batch_delete_project()
\ No newline at end of file
# Time : 2024/7/21 16:06
# Author : wangchao
# File : Test_VariableManagement.py
# Description : 变量管理测试类
import os
from time import sleep
import allure
import pytest
import re
from playwright.sync_api import expect
from BasePage.BasePage import BasePage
from BuildInLibrary.BuildInLibrary import BuildInLibrary
from Common.AllurePretty import PrettyAllure
from Pages.ProjectManagementPage.ProjectManagementPage import ProjectManagementPage
from Pages.VariableManagementPage.VariableManagementPage import VariableManagementPage
from Config import Config
from Utils.ReadYaml import ReadYaml
from Utils.VisualRegression import visual_regression
@allure.epic("CMS2.0UI自动化测试")
@allure.feature("变量管理")
class TestVariableManagement:
@allure.story("全局导入变量")
@PrettyAllure.PrettyAllureCaseWarpper
@pytest.mark.run(order=1)
@pytest.mark.parametrize("CaseData", ReadYaml(
os.path.join(Config.test_datas_dir, "VariableManagementData/TestGlobalImportVariable.yaml")).read())
def test_global_import_variable(self, page, CaseData: dict):
bp = BasePage(page)
vmp = VariableManagementPage(page)
bp._goto_url_variable_management()
vmp.import_global_variable_group(CaseData["参数化值-导入文件名"])
expect(vmp.import_variable_success_toast_locator).to_have_text("导入成功") #断言导入成功
@allure.story("全局导出变量")
@pytest.mark.run(order=1)
@PrettyAllure.PrettyAllureCaseWarpper
@pytest.mark.parametrize("CaseData", ReadYaml(
os.path.join(Config.test_datas_dir, "VariableManagementData/TestGlobalExportVariable.yaml")).read())
def test_global_export_variable(self, page, CaseData: dict):
bp = BasePage(page)
vmp = VariableManagementPage(page)
bp._goto_url_variable_management()
vmp.export_global_variable_group()
expect(vmp.export_variable_success_toast_locator).to_have_text("导出成功") #断言导出成功
@allure.story("IO变量导入")
@pytest.mark.run(order=1)
@PrettyAllure.PrettyAllureCaseWarpper
@pytest.mark.parametrize("CaseData", ReadYaml(
os.path.join(Config.test_datas_dir, "VariableManagementData/TestIOVariableImport.yaml")).read())
def test_io_variable_import(self, page, CaseData: dict):
bp = BasePage(page)
vmp = VariableManagementPage(page)
bp._goto_url_variable_management()
vmp.import_io_variable_group(CaseData["参数化值-导入文件名"])
expect(vmp.import_variable_success_toast_locator).to_have_text("导入成功") #断言导入成功
@allure.story("IO变量导出")
@pytest.mark.run(order=1)
@PrettyAllure.PrettyAllureCaseWarpper
@pytest.mark.parametrize("CaseData", ReadYaml(
os.path.join(Config.test_datas_dir, "VariableManagementData/TestIOVariableExport.yaml")).read())
def test_io_variable_export(self, page, CaseData: dict):
bp = BasePage(page)
vmp = VariableManagementPage(page)
bp._goto_url_variable_management()
vmp.export_io_variable_group()
expect(vmp.export_variable_success_toast_locator).to_have_text("导出成功") #断言导出成功
@allure.story("内部变量导入")
@pytest.mark.run(order=1)
@PrettyAllure.PrettyAllureCaseWarpper
@pytest.mark.parametrize("CaseData", ReadYaml(os.path.join(Config.test_datas_dir, "VariableManagementData/TestInternalVariableImport.yaml")).read())
def test_inner_variable_import(self, page, CaseData: dict):
bp = BasePage(page)
vmp = VariableManagementPage(page)
bp._goto_url_variable_management()
vmp.import_internal_variable_group(CaseData["参数化值-导入文件名"])
expect(vmp.import_variable_success_toast_locator).to_have_text("导入成功")
@allure.story("内部变量导出")
@pytest.mark.run(order=1)
@PrettyAllure.PrettyAllureCaseWarpper
@pytest.mark.parametrize("CaseData", ReadYaml(os.path.join(Config.test_datas_dir, "VariableManagementData/TestInternalVariableExport.yaml")).read())
def test_inner_variable_export(self, page, CaseData: dict):
bp = BasePage(page)
vmp = VariableManagementPage(page)
bp._goto_url_variable_management()
vmp.export_internal_variable_group()
expect(vmp.export_variable_success_toast_locator).to_have_text("导出成功")
"""批量处理变量"""
@allure.story("批量处理变量—修改变量")
@pytest.mark.run(order=1)
@PrettyAllure.PrettyAllureCaseWarpper
@pytest.mark.parametrize("CaseData", ReadYaml(os.path.join(Config.test_datas_dir, "VariableManagementData/TestBatchProcessVariable.yaml")).read())
def test_batch_process_variable(self, page, CaseData: dict):
bp = BasePage(page)
vmp = VariableManagementPage(page)
bp._goto_url_variable_management()
#page.pause()
vmp.batch_handle_variable()
expect(vmp.batch_modification_variable_success_toast_locator).to_have_text("保存成功")
-
用例编号: history_library_data_backup_001
用例标题: 历史库数据备份
模块: 历史库管理
功能: 历史库数据备份
优先级:
是否执行: Y
断言元素定位:
\ No newline at end of file
-
用例编号: history_library_data_clear_001
用例标题: 历史库数据清除
模块: 历史库管理
功能: 历史库数据清除
优先级:
是否执行: Y
断言元素定位:
\ No newline at end of file
-
用例编号: page_component_rectangle_001
用例标题: 页面组件-矩形框
模块: 画面管理
功能: 页面组件-矩形框
优先级:
是否执行: Y
参数化值-页面名称: '矩形框测试{{$timetime}}'
\ No newline at end of file
-
用例编号: page_component_rectangle_set_x_axis_001
用例标题: 页面组件-矩形框-设置x轴值
模块: 画面管理
功能: 页面组件-矩形框-属性设置
优先级:
是否执行: Y
参数化值-页面名称: '矩形框位置属性测试{{$timetime}}'
参数化值-x轴值: '300'
-
用例编号: page_component_trend_curve_001
用例标题: 页面组件-运行组件-趋势曲线
模块: 画面管理
功能: 页面组件-运行组件-趋势曲线
优先级:
是否执行: Y
参数化值-页面名称: '曲线曲线测试{{$timetime}}'
\ No newline at end of file
-
用例编号: page_export_001
用例标题: 画面导出
模块: 画面管理
功能: 画面管理_画面导出
优先级:
是否执行: Y
参数化值-页面名称: '画面导入导出测试'
断言元素定位:
\ No newline at end of file
-
用例编号: page_import_001
用例标题: 画面导入
模块: 画面管理
功能: 画面管理_画面导入
优先级:
是否执行: Y
参数化值-页面名称: '画面导入导出测试'
参数化值-导入文件名: '/Users/wangchao/Downloads/导出工程页面_20240730095915.zip'
断言元素定位:
\ No newline at end of file
-
用例编号: TestBatchDeleteProject01
用例标题: 批量删除工程
模块: 工程管理
功能: 批量删除工程
优先级:
是否执行: Y
断言元素定位:
\ No newline at end of file
-
用例编号: CreateBlankProject01
用例标题: 新建空白工程
模块: 工程管理
功能: 新建空白工程
优先级:
是否执行: Y
参数化值-工程名: 'CMS自动化测试工程{{$uuid}}'
断言元素定位: //*[@id="detailTable_18"]/span
-
用例编号: CreateProjectFolder01
用例标题: 创建工程文件夹
模块: 工程管理
功能: 创建工程文件夹
优先级:
是否执行: Y
参数化值-文件夹名: '工程文件夹{{$timestamp}}'
\ No newline at end of file
-
用例编号: TestDataBackup01
用例标题: 工程数据备份
模块: 工程管理
功能: 工程数据备份
优先级:
是否执行: Y
参数化值-工程数据备份文件名: 'CMS工程备份{{$timestamp}}'
断言元素定位:
\ No newline at end of file
-
用例编号: TestDataClear01
用例标题: 工程数据清除
模块: 工程管理
功能: 工程数据清除
优先级:
是否执行: Y
断言元素定位:
\ No newline at end of file
-
用例编号: TestDataRecovery01
用例标题: 工程数据恢复
模块: 工程管理
功能: 工程数据恢复
优先级:
是否执行: Y
参数化值-工程数据备份文件名: '/Users/wangchao/Downloads/222222.cmsbak'
断言元素定位:
\ No newline at end of file
-
用例编号: EditeProject_MoveTo01
用例标题: 移动工程
模块: 工程管理
功能: 编辑工程-移动到
优先级:
是否执行: Y
参数化值-工程名: CMS2.0UI自动化
\ No newline at end of file
-
用例编号: EditeProject_Rename01
用例标题: 重命名工程
模块: 工程管理
功能: 编辑工程-重命名
优先级:
是否执行: Y
参数化值-工程名: 'CMS自动化测试工程${{uuid}}'
\ No newline at end of file
-
用例编号: ImportLocalProject01
用例标题: 导入本地工程
模块: 工程管理
功能: 导入本地工程
优先级:
是否执行: Y
-
用例编号: SearchProject01
用例标题: 搜索指定工程
模块: 工程管理
功能: 搜索工程
优先级:
是否执行: Y
参数化值-工程名: CMS2.0UI自动化
\ No newline at end of file
-
用例编号: batch_process_variable_001
用例标题: 批量处理变量_修改变量读写方式
模块: 变量管理
功能: 批量处理变量_修改变量读写方式
优先级:
是否执行: Y
断言元素定位:
\ No newline at end of file
-
用例编号: global_export_variable_001
用例标题: 全局导出变量
模块: 变量管理
功能: 全局导出变量
优先级:
是否执行: Y
断言元素定位:
\ No newline at end of file
-
用例编号: global_import_variable_001
用例标题: 全局导入变量
模块: 变量管理
功能: 全局导入变量
优先级:
是否执行: Y
参数化值-导入文件名: '/Users/wangchao/PythonProjects/CMS/CMS2.0_UI_AUTO_TEST/ProjectData/VariableData/全局变量导入.xlsx'
断言元素定位:
\ No newline at end of file
-
用例编号: IO_variable_export_001
用例标题: IO变量导出
模块: 变量管理
功能: IO变量导出
优先级:
是否执行: Y
断言元素定位:
\ No newline at end of file
-
用例编号: IO_variable_import_001
用例标题: IO变量导入
模块: 变量管理
功能: IO变量导入
优先级:
是否执行: Y
参数化值-导入文件名: '/Users/wangchao/PythonProjects/CMS/CMS2.0_UI_AUTO_TEST/ProjectData/VariableData/IO变量导入.xlsx'
断言元素定位:
\ No newline at end of file
-
用例编号: Inner_variable_export_001
用例标题: 内部变量导出
模块: 变量管理
功能: 内部变量导出
优先级:
是否执行: Y
断言元素定位:
\ No newline at end of file
-
用例编号: Inner_variable_import_001
用例标题: 内部变量导入
模块: 变量管理
功能: 内部变量导入
优先级:
是否执行: Y
参数化值-导入文件名: '/Users/wangchao/PythonProjects/CMS/CMS2.0_UI_AUTO_TEST/ProjectData/VariableData/内部变量导入.xlsx'
断言元素定位:
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
INFO log:logger_util.py:72
-------------测试用例开始-----------
INFO log:logger_util.py:72 进行图片比对...
INFO log:logger_util.py:72 实际图片:静态文本_设置右对齐_实际图.png, 预期图片:静态文本_设置右对齐_预期图.png
INFO log:logger_util.py:72 比对结果:True
INFO log:logger_util.py:72
-------------测试用例结束-----------
\ No newline at end of file
"Epic","Feature","Story","FAILED","BROKEN","PASSED","SKIPPED","UNKNOWN"
"CMS2.0UI自动化测试","静态文本组件","静态文本-设置右对齐","0","0","1","0","0"
{"uid":"b1a8273437954620fa374b796ffaacdd","name":"behaviors","children":[{"name":"CMS2.0UI自动化测试","children":[{"name":"静态文本组件","children":[{"name":"静态文本-设置右对齐","children":[{"name":"test_StaticText_set_right_aligned[\\u9759\\u6001\\u6587\\u672c-\\u9759\\u6001\\u6587\\u672c_\\u8bbe\\u7f6e\\u53f3\\u5bf9\\u9f50_\\u5b9e\\u9645\\u56fe-\\u9759\\u6001\\u6587\\u672c_\\u8bbe\\u7f6e\\u53f3\\u5bf9\\u9f50_\\u9884\\u671f\\u56fe]","uid":"da092d227c594667","parentUid":"a535d3ea30e203c41c679930d4ed34d8","status":"passed","time":{"start":1724639375964,"stop":1724639382338,"duration":6374},"flaky":false,"newFailed":false,"newPassed":false,"newBroken":false,"retriesCount":0,"retriesStatusChange":false,"parameters":["'静态文本_设置右对齐_实际图'","'静态文本_设置右对齐_预期图'","'静态文本'"]}],"uid":"a535d3ea30e203c41c679930d4ed34d8"}],"uid":"055b8ec823318ab0d00d99283c5e0b95"}],"uid":"390d43add3d704eac84707ba62ecc607"}]}
\ No newline at end of file
{"uid":"4b4757e66a1912dae1a509f688f20b0f","name":"categories","children":[]}
\ No newline at end of file
{"uid":"83edc06c07f9ae9e47eb6dd1b683e4e2","name":"packages","children":[{"name":"TestCases.Test_StaticText","children":[{"name":"test_StaticText_set_right_aligned[\\u9759\\u6001\\u6587\\u672c-\\u9759\\u6001\\u6587\\u672c_\\u8bbe\\u7f6e\\u53f3\\u5bf9\\u9f50_\\u5b9e\\u9645\\u56fe-\\u9759\\u6001\\u6587\\u672c_\\u8bbe\\u7f6e\\u53f3\\u5bf9\\u9f50_\\u9884\\u671f\\u56fe]","uid":"da092d227c594667","parentUid":"ea31ef4982075d500f62b88d85860f67","status":"passed","time":{"start":1724639375964,"stop":1724639382338,"duration":6374},"flaky":false,"newFailed":false,"newPassed":false,"newBroken":false,"retriesCount":0,"retriesStatusChange":false,"parameters":["'静态文本_设置右对齐_实际图'","'静态文本_设置右对齐_预期图'","'静态文本'"]}],"uid":"TestCases.Test_StaticText"}]}
\ No newline at end of file
"Status","Start Time","Stop Time","Duration in ms","Parent Suite","Suite","Sub Suite","Test Class","Test Method","Name","Description"
"passed","Mon Aug 26 10:29:35 CST 2024","Mon Aug 26 10:29:42 CST 2024","6374","TestCases","Test_StaticText","Test_StaticText","","","test_StaticText_set_right_aligned[\u9759\u6001\u6587\u672c-\u9759\u6001\u6587\u672c_\u8bbe\u7f6e\u53f3\u5bf9\u9f50_\u5b9e\u9645\u56fe-\u9759\u6001\u6587\u672c_\u8bbe\u7f6e\u53f3\u5bf9\u9f50_\u9884\u671f\u56fe]",""
{"uid":"98d3104e051c652961429bf95fa0b5d6","name":"suites","children":[{"name":"TestCases","children":[{"name":"Test_StaticText","children":[{"name":"Test_StaticText","children":[{"name":"test_StaticText_set_right_aligned[\\u9759\\u6001\\u6587\\u672c-\\u9759\\u6001\\u6587\\u672c_\\u8bbe\\u7f6e\\u53f3\\u5bf9\\u9f50_\\u5b9e\\u9645\\u56fe-\\u9759\\u6001\\u6587\\u672c_\\u8bbe\\u7f6e\\u53f3\\u5bf9\\u9f50_\\u9884\\u671f\\u56fe]","uid":"da092d227c594667","parentUid":"bf12649697c503c908ae789519e69777","status":"passed","time":{"start":1724639375964,"stop":1724639382338,"duration":6374},"flaky":false,"newFailed":false,"newPassed":false,"newBroken":false,"retriesCount":0,"retriesStatusChange":false,"parameters":["'静态文本_设置右对齐_实际图'","'静态文本_设置右对齐_预期图'","'静态文本'"]}],"uid":"bf12649697c503c908ae789519e69777"}],"uid":"ba6ad6cc9f498b4dc5f7b391afcf36c7"}],"uid":"0408d924bc3d43de55080f45620fd22e"}]}
\ No newline at end of file
{"uid":"da092d227c594667","name":"test_StaticText_set_right_aligned[\\u9759\\u6001\\u6587\\u672c-\\u9759\\u6001\\u6587\\u672c_\\u8bbe\\u7f6e\\u53f3\\u5bf9\\u9f50_\\u5b9e\\u9645\\u56fe-\\u9759\\u6001\\u6587\\u672c_\\u8bbe\\u7f6e\\u53f3\\u5bf9\\u9f50_\\u9884\\u671f\\u56fe]","fullName":"TestCases.Test_StaticText.Test_StaticText#test_StaticText_set_right_aligned","historyId":"99685ed6fb73092f5095fb6c1fed81f4","time":{"start":1724639375964,"stop":1724639382338,"duration":6374},"status":"passed","flaky":false,"newFailed":false,"newBroken":false,"newPassed":false,"retriesCount":0,"retriesStatusChange":false,"beforeStages":[{"name":"_verify_url","time":{"start":1724639358553,"stop":1724639358553,"duration":0},"status":"passed","steps":[],"attachments":[],"parameters":[],"hasContent":false,"attachmentsCount":0,"shouldDisplayMessage":false,"stepsCount":0},{"name":"base_url","time":{"start":1724639358553,"stop":1724639358553,"duration":0},"status":"passed","steps":[],"attachments":[],"parameters":[],"hasContent":false,"attachmentsCount":0,"shouldDisplayMessage":false,"stepsCount":0},{"name":"pytestconfig","time":{"start":1724639358554,"stop":1724639358554,"duration":0},"status":"passed","steps":[],"attachments":[],"parameters":[],"hasContent":false,"attachmentsCount":0,"shouldDisplayMessage":false,"stepsCount":0},{"name":"创建工程","time":{"start":1724639358554,"stop":1724639374977,"duration":16423},"status":"passed","steps":[],"attachments":[],"parameters":[],"hasContent":false,"attachmentsCount":0,"shouldDisplayMessage":false,"stepsCount":0},{"name":"delete_output_dir","time":{"start":1724639358554,"stop":1724639358554,"duration":0},"status":"passed","steps":[],"attachments":[],"parameters":[],"hasContent":false,"attachmentsCount":0,"shouldDisplayMessage":false,"stepsCount":0},{"name":"upload_image","time":{"start":1724639374977,"stop":1724639374993,"duration":16},"status":"passed","steps":[],"attachments":[],"parameters":[],"hasContent":false,"attachmentsCount":0,"shouldDisplayMessage":false,"stepsCount":0},{"name":"Generate pageobject and video of the operation","time":{"start":1724639374993,"stop":1724639375683,"duration":690},"status":"passed","steps":[],"attachments":[],"parameters":[],"hasContent":false,"attachmentsCount":0,"shouldDisplayMessage":false,"stepsCount":0},{"name":"create_and_destroy_page","time":{"start":1724639375684,"stop":1724639375964,"duration":280},"status":"passed","steps":[],"attachments":[],"parameters":[],"hasContent":false,"attachmentsCount":0,"shouldDisplayMessage":false,"stepsCount":0}],"testStage":{"status":"passed","steps":[{"name":"实际截图","time":{"start":1724639382336,"stop":1724639382337,"duration":1},"status":"passed","steps":[],"attachments":[{"uid":"78c54aec166f6a31","name":"静态文本_设置右对齐_实际图","source":"78c54aec166f6a31.png","type":"image/png","size":6542}],"parameters":[],"hasContent":true,"attachmentsCount":1,"shouldDisplayMessage":false,"stepsCount":0},{"name":"预期截图","time":{"start":1724639382337,"stop":1724639382337,"duration":0},"status":"passed","steps":[],"attachments":[{"uid":"8a2011f5687f3dc9","name":"静态文本_设置右对齐_预期图","source":"8a2011f5687f3dc9.png","type":"image/png","size":6542}],"parameters":[],"hasContent":true,"attachmentsCount":1,"shouldDisplayMessage":false,"stepsCount":0}],"attachments":[{"uid":"2629d55742e14e1a","name":"log","source":"2629d55742e14e1a.txt","type":"text/plain","size":402}],"parameters":[],"hasContent":true,"attachmentsCount":3,"shouldDisplayMessage":false,"stepsCount":2},"afterStages":[{"name":"create_and_destroy_page::0","time":{"start":1724639382340,"stop":1724639382591,"duration":251},"status":"passed","steps":[],"attachments":[],"parameters":[],"hasContent":false,"attachmentsCount":0,"shouldDisplayMessage":false,"stepsCount":0},{"name":"Generate pageobject and video of the operation::0","time":{"start":1724639382592,"stop":1724639382939,"duration":347},"status":"passed","steps":[],"attachments":[{"uid":"86fdb211628baa28","name":"CMS操作的屏幕录制","source":"86fdb211628baa28.webm","type":"video/webm","size":533949}],"parameters":[],"hasContent":true,"attachmentsCount":1,"shouldDisplayMessage":false,"stepsCount":0}],"labels":[{"name":"story","value":"静态文本-设置右对齐"},{"name":"feature","value":"静态文本组件"},{"name":"epic","value":"CMS2.0UI自动化测试"},{"name":"parentSuite","value":"TestCases"},{"name":"suite","value":"Test_StaticText"},{"name":"subSuite","value":"Test_StaticText"},{"name":"host","value":"DESKTOP-V6TD7H4"},{"name":"thread","value":"13940-MainThread"},{"name":"framework","value":"pytest"},{"name":"language","value":"cpython3"},{"name":"package","value":"TestCases.Test_StaticText"},{"name":"resultFormat","value":"allure2"}],"parameters":[{"name":"actual_img_name","value":"'静态文本_设置右对齐_实际图'"},{"name":"expect_img_name","value":"'静态文本_设置右对齐_预期图'"},{"name":"filename","value":"'静态文本'"}],"links":[],"hidden":false,"retry":false,"extra":{"severity":"normal","retries":[],"categories":[],"tags":[]},"source":"da092d227c594667.json","parameterValues":["'静态文本_设置右对齐_实际图'","'静态文本_设置右对齐_预期图'","'静态文本'"]}
\ No newline at end of file
{"uid":"ab17fc5a4eb3bca4b216b548c7f9fcbc","name":"timeline","children":[{"name":"DESKTOP-V6TD7H4","children":[{"name":"13940-MainThread","children":[{"name":"test_StaticText_set_right_aligned[\\u9759\\u6001\\u6587\\u672c-\\u9759\\u6001\\u6587\\u672c_\\u8bbe\\u7f6e\\u53f3\\u5bf9\\u9f50_\\u5b9e\\u9645\\u56fe-\\u9759\\u6001\\u6587\\u672c_\\u8bbe\\u7f6e\\u53f3\\u5bf9\\u9f50_\\u9884\\u671f\\u56fe]","uid":"da092d227c594667","parentUid":"5e9779f5cedb755e20cbc9a08e4dd5e9","status":"passed","time":{"start":1724639375964,"stop":1724639382338,"duration":6374},"flaky":false,"newFailed":false,"newPassed":false,"newBroken":false,"retriesCount":0,"retriesStatusChange":false,"parameters":["'静态文本_设置右对齐_实际图'","'静态文本_设置右对齐_预期图'","'静态文本'"]}],"uid":"5e9779f5cedb755e20cbc9a08e4dd5e9"}],"uid":"aeb1e8552f5518f4437a45a499712f24"}]}
\ No newline at end of file
launch_status failed=0 1724639385000000000
launch_status broken=0 1724639385000000000
launch_status passed=1 1724639385000000000
launch_status skipped=0 1724639385000000000
launch_status unknown=0 1724639385000000000
launch_time duration=6374 1724639385000000000
launch_time min_duration=6374 1724639385000000000
launch_time max_duration=6374 1724639385000000000
launch_time sum_duration=6374 1724639385000000000
launch_retries retries=0 1724639385000000000
launch_retries run=1 1724639385000000000
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Allure Report summary mail</title>
</head>
<body>
Mail body
</body>
</html>
launch_status_failed 0
launch_status_broken 0
launch_status_passed 1
launch_status_skipped 0
launch_status_unknown 0
launch_time_duration 6374
launch_time_min_duration 6374
launch_time_max_duration 6374
launch_time_sum_duration 6374
launch_retries_retries 0
launch_retries_run 1
[{"data":{}}]
\ No newline at end of file
[{"data":{"duration":6374}}]
\ No newline at end of file
[{"data":{"failed":0,"broken":0,"skipped":0,"passed":1,"unknown":0,"total":1}}]
\ No newline at end of file
{"99685ed6fb73092f5095fb6c1fed81f4":{"statistic":{"failed":0,"broken":0,"skipped":0,"passed":1,"unknown":0,"total":1},"items":[{"uid":"da092d227c594667","status":"passed","time":{"start":1724639375964,"stop":1724639382338,"duration":6374}}]}}
\ No newline at end of file
[{"data":{"run":1,"retry":0}}]
\ No newline at end of file
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="utf-8">
<title>Allure Report</title>
<link rel="favicon" href="favicon.ico?v=2">
<link rel="stylesheet" type="text/css" href="styles.css">
<link rel="stylesheet" href="plugins/screen-diff/styles.css">
<link rel="stylesheet" href="plugins/custom-logo/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="plugins/behaviors/index.js"></script>
<script src="plugins/packages/index.js"></script>
<script src="plugins/screen-diff/index.js"></script>
</body>
</html>
'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.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
}));
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 128 128" version="1.1" viewBox="0 0 128 128" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="Layer_1"><rect fill="#F4F5F5" height="1520" opacity="0" width="727.938" x="-59.984" y="-351"/></g><g id="Layer_2"><g><circle cx="64" cy="64" fill="#6E9583" r="64"/><g><defs><circle cx="64" cy="64" id="SVGID_3_" r="64"/></defs><clipPath id="SVGID_2_"><use overflow="visible" xlink:href="#SVGID_3_"/></clipPath><polygon clip-path="url(#SVGID_2_)" fill="#648778" points="93.572,29.677 128,64 128,128 54.36,128 33.341,106.906 "/></g><path d="M84.044,20H36.018C33.579,20,32,22.11,32,24.549v78.903c0,2.439,1.579,4.549,4.018,4.549h55.989 c2.439,0,4.018-2.11,4.018-4.549V32.143L84.044,20z" fill="#F1F1F1"/><g><defs><path d="M84.044,20H36.018C33.579,20,32,22.11,32,24.549v78.903c0,2.439,1.579,4.549,4.018,4.549h55.989 c2.439,0,4.018-2.11,4.018-4.549V32.143L84.044,20z" id="SVGID_5_"/></defs><clipPath id="SVGID_4_"><use overflow="visible" xlink:href="#SVGID_5_"/></clipPath><g clip-path="url(#SVGID_4_)"><polygon fill="#DDE1F1" points="50.948,67.621 65.539,82.042 42.971,83.087 49.777,90 42.971,91.087 49.277,97.555 42.971,99.087 53.027,109.305 97.684,109.305 97.684,75.707 97.075,54.055 81.059,37.758 70.97,44.918 62.684,35.107 "/></g></g><path d="M88.186,32.138l7.839,0.005L84.044,20v7.96C84.044,30.398,85.769,32.138,88.186,32.138z" fill="#C2DFC9"/><path d="M84,83.5H44c-0.828,0-1.5-0.672-1.5-1.5s0.672-1.5,1.5-1.5h40c0.828,0,1.5,0.672,1.5,1.5 S84.828,83.5,84,83.5z" fill="#495260"/><path d="M84,91.5H44c-0.828,0-1.5-0.672-1.5-1.5s0.672-1.5,1.5-1.5h40c0.828,0,1.5,0.672,1.5,1.5 S84.828,91.5,84,91.5z" fill="#495260"/><path d="M84,99.5H44c-0.828,0-1.5-0.672-1.5-1.5s0.672-1.5,1.5-1.5h40c0.828,0,1.5,0.672,1.5,1.5 S84.828,99.5,84,99.5z" fill="#495260"/><g><path d="M69.568,31.844l-1.319,11.303c2.314,0.88,4.242,2.728,5.132,5.245c0.573,1.619,0.631,3.292,0.274,4.851 l10.257,4.895c0.527,0.252,1.155-0.023,1.329-0.581c1.308-4.188,1.323-8.819-0.253-13.273 c-2.379-6.723-7.827-11.477-14.212-13.254C70.21,30.872,69.636,31.26,69.568,31.844z" fill="#0E9CD9"/><path d="M66.68,59.901c-3.653,0.668-7.398-1.12-9.176-4.38c-1.094-2.006-1.312-4.174-0.858-6.157L46.39,44.469 c-0.527-0.251-1.155,0.023-1.329,0.58c-1.286,4.118-1.322,8.663,0.175,13.049c3.701,10.842,15.624,16.783,26.503,13.191 c4.655-1.537,8.399-4.531,10.911-8.3c0.324-0.486,0.141-1.147-0.385-1.398l-10.257-4.896 C70.751,58.296,68.929,59.49,66.68,59.901z" fill="#E95037"/><path d="M62.239,43.074c0.734-0.26,1.479-0.405,2.22-0.464l1.316-11.275c0.067-0.576-0.389-1.08-0.968-1.071 c-2.218,0.035-4.469,0.421-6.676,1.202c-4.455,1.576-8.045,4.5-10.479,8.151c-0.324,0.486-0.142,1.147,0.385,1.399l10.257,4.895 C59.282,44.654,60.62,43.647,62.239,43.074z" fill="#69B32D"/><g><defs><path d="M69.695,30.76l-1.446,12.387c2.314,0.88,4.242,2.728,5.132,5.245c0.573,1.619,0.631,3.292,0.274,4.851 l10.257,4.895c0.527,0.252,1.155-0.023,1.329-0.581c1.308-4.188,1.323-8.819-0.253-13.273 C82.476,37.185,76.541,32.281,69.695,30.76z M66.68,59.901c-3.653,0.668-7.398-1.12-9.176-4.38 c-1.094-2.006-1.312-4.174-0.858-6.157L46.39,44.469c-0.527-0.251-1.155,0.023-1.329,0.58 c-1.286,4.118-1.322,8.663,0.175,13.049c3.701,10.842,15.624,16.783,26.503,13.191c4.655-1.537,8.399-4.531,10.911-8.3 c0.324-0.486,0.141-1.147-0.385-1.398l-10.257-4.896C70.751,58.296,68.929,59.49,66.68,59.901z M62.239,43.074 c0.734-0.26,1.479-0.405,2.22-0.464l1.316-11.275c0.067-0.576-0.389-1.08-0.968-1.071c-2.218,0.035-4.469,0.421-6.676,1.202 c-4.455,1.576-8.045,4.5-10.479,8.151c-0.324,0.486-0.142,1.147,0.385,1.399l10.257,4.895 C59.282,44.654,60.62,43.647,62.239,43.074z" id="SVGID_7_"/></defs><clipPath id="SVGID_6_"><use overflow="visible" xlink:href="#SVGID_7_"/></clipPath><circle clip-path="url(#SVGID_6_)" cx="65.151" cy="51.304" fill="#FFFFFF" opacity="0.4" r="12.507"/></g></g></g></g></svg>
\ No newline at end of file
/*
.side-nav__brand {
background: url('custom-logo.svg') no-repeat left center !important;
margin-left: 10px;
}
*/
.side -nav__brand{
background:url('syclogo.png') no-repeat left center !important;
margin - left: 10px;
height: 90px;
background - size: contain !important;
}
.side -nav__brand -text{
display: none;
}
\ No newline at end of file
'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.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'
});
})
});
(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',
});
})();
.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;
}
This source diff could not be displayed because it is too large. You can view the blob instead.
{"total":1,"items":[{"uid":"390d43add3d704eac84707ba62ecc607","name":"CMS2.0UI自动化测试","statistic":{"failed":0,"broken":0,"skipped":0,"passed":1,"unknown":0,"total":1}}]}
\ No newline at end of file
[{"data":{}}]
\ No newline at end of file
{"total":0,"items":[]}
\ No newline at end of file
[{"data":{"duration":6374}}]
\ No newline at end of file
[{"uid":"da092d227c594667","name":"test_StaticText_set_right_aligned[\\u9759\\u6001\\u6587\\u672c-\\u9759\\u6001\\u6587\\u672c_\\u8bbe\\u7f6e\\u53f3\\u5bf9\\u9f50_\\u5b9e\\u9645\\u56fe-\\u9759\\u6001\\u6587\\u672c_\\u8bbe\\u7f6e\\u53f3\\u5bf9\\u9f50_\\u9884\\u671f\\u56fe]","time":{"start":1724639375964,"stop":1724639382338,"duration":6374},"status":"passed","severity":"normal"}]
\ No newline at end of file
[{"data":{"failed":0,"broken":0,"skipped":0,"passed":1,"unknown":0,"total":1}}]
\ No newline at end of file
[]
\ No newline at end of file
[{"data":{"run":1,"retry":0}}]
\ No newline at end of file
[{"uid":"da092d227c594667","name":"test_StaticText_set_right_aligned[\\u9759\\u6001\\u6587\\u672c-\\u9759\\u6001\\u6587\\u672c_\\u8bbe\\u7f6e\\u53f3\\u5bf9\\u9f50_\\u5b9e\\u9645\\u56fe-\\u9759\\u6001\\u6587\\u672c_\\u8bbe\\u7f6e\\u53f3\\u5bf9\\u9f50_\\u9884\\u671f\\u56fe]","time":{"start":1724639375964,"stop":1724639382338,"duration":6374},"status":"passed","severity":"normal"}]
\ No newline at end of file
[{"uid":"da092d227c594667","name":"test_StaticText_set_right_aligned[\\u9759\\u6001\\u6587\\u672c-\\u9759\\u6001\\u6587\\u672c_\\u8bbe\\u7f6e\\u53f3\\u5bf9\\u9f50_\\u5b9e\\u9645\\u56fe-\\u9759\\u6001\\u6587\\u672c_\\u8bbe\\u7f6e\\u53f3\\u5bf9\\u9f50_\\u9884\\u671f\\u56fe]","time":{"start":1724639375964,"stop":1724639382338,"duration":6374},"status":"passed","severity":"normal"}]
\ No newline at end of file
{"total":1,"items":[{"uid":"0408d924bc3d43de55080f45620fd22e","name":"TestCases","statistic":{"failed":0,"broken":0,"skipped":0,"passed":1,"unknown":0,"total":1}}]}
\ No newline at end of file
{"reportName":"Allure Report","testRuns":[],"statistic":{"failed":0,"broken":0,"skipped":0,"passed":1,"unknown":0,"total":1},"time":{"start":1724639375964,"stop":1724639382338,"duration":6374,"minDuration":6374,"maxDuration":6374,"sumDuration":6374}}
\ No newline at end of file
{"uuid": "d08ff015-0a51-47a6-9988-7a9bf337e93b", "befores": [{"name": "actual_img_name", "status": "passed", "start": 1724639375964, "stop": 1724639375964}], "start": 1724639375964, "stop": 1724639382338}
\ No newline at end of file
{"uuid": "896d041a-acc9-46e1-870c-e01cf5ba6fe9", "befores": [{"name": "expect_img_name", "status": "passed", "start": 1724639375964, "stop": 1724639375964}], "start": 1724639375964, "stop": 1724639382338}
\ No newline at end of file
INFO log:logger_util.py:72
-------------测试用例开始-----------
INFO log:logger_util.py:72 进行图片比对...
INFO log:logger_util.py:72 实际图片:静态文本_设置右对齐_实际图.png, 预期图片:静态文本_设置右对齐_预期图.png
INFO log:logger_util.py:72 比对结果:True
INFO log:logger_util.py:72
-------------测试用例结束-----------
\ No newline at end of file
{"uuid": "1a320878-6ae9-49b0-b193-5841ce7420c8", "children": ["f15ce405-3aa0-42f2-859e-fbb09683827d"], "befores": [{"name": "pytestconfig", "status": "passed", "start": 1724639358554, "stop": 1724639358554}], "start": 1724639358554, "stop": 1724639382942}
\ No newline at end of file
{"uuid": "084f1ff3-412f-4f8c-a0dd-e13e766c2f21", "befores": [{"name": "filename", "status": "passed", "start": 1724639375964, "stop": 1724639375964}], "start": 1724639375964, "stop": 1724639382339}
\ No newline at end of file
{"uuid": "8f42a5d6-eba9-4c06-8dd1-530e444a0788", "children": ["f15ce405-3aa0-42f2-859e-fbb09683827d"], "befores": [{"name": "_verify_url", "status": "passed", "start": 1724639358553, "stop": 1724639358553}], "start": 1724639358553, "stop": 1724639382943}
\ No newline at end of file
{"uuid": "d72ae298-c0eb-4485-8a93-7fed7a9e9514", "children": ["f15ce405-3aa0-42f2-859e-fbb09683827d"], "befores": [{"name": "创建工程", "status": "passed", "start": 1724639358554, "stop": 1724639374977}], "start": 1724639358554, "stop": 1724639382940}
\ No newline at end of file
{"uuid": "041810df-a5ad-4cba-9df6-9b6329c7f438", "children": ["f15ce405-3aa0-42f2-859e-fbb09683827d"], "befores": [{"name": "create_and_destroy_page", "status": "passed", "start": 1724639375684, "stop": 1724639375964}], "afters": [{"name": "create_and_destroy_page::0", "status": "passed", "start": 1724639382340, "stop": 1724639382591}], "start": 1724639375684, "stop": 1724639382591}
\ No newline at end of file
{"uuid": "91fd4754-0a4d-44fe-b863-a021d2d8ffbc", "children": ["f15ce405-3aa0-42f2-859e-fbb09683827d"], "befores": [{"name": "upload_image", "status": "passed", "start": 1724639374977, "stop": 1724639374993}], "start": 1724639374977, "stop": 1724639382940}
\ No newline at end of file
{"uuid": "f0ef4af2-e794-4507-a01e-9724043d4b9d", "children": ["f15ce405-3aa0-42f2-859e-fbb09683827d"], "befores": [{"name": "delete_output_dir", "status": "passed", "start": 1724639358554, "stop": 1724639358554}], "start": 1724639358554, "stop": 1724639382940}
\ No newline at end of file
{"uuid": "5ed3218b-5f8b-4158-8585-c596ee57a2a4", "children": ["f15ce405-3aa0-42f2-859e-fbb09683827d"], "befores": [{"name": "base_url", "status": "passed", "start": 1724639358553, "stop": 1724639358553}], "start": 1724639358553, "stop": 1724639382943}
\ No newline at end of file
{"name": "test_StaticText_set_right_aligned[\\u9759\\u6001\\u6587\\u672c-\\u9759\\u6001\\u6587\\u672c_\\u8bbe\\u7f6e\\u53f3\\u5bf9\\u9f50_\\u5b9e\\u9645\\u56fe-\\u9759\\u6001\\u6587\\u672c_\\u8bbe\\u7f6e\\u53f3\\u5bf9\\u9f50_\\u9884\\u671f\\u56fe]", "status": "passed", "steps": [{"name": "实际截图", "status": "passed", "attachments": [{"name": "静态文本_设置右对齐_实际图", "source": "163aaaec-04b5-4ee5-8d24-0fe1dcde4d70-attachment.png", "type": "image/png"}], "start": 1724639382336, "stop": 1724639382337}, {"name": "预期截图", "status": "passed", "attachments": [{"name": "静态文本_设置右对齐_预期图", "source": "f7494962-7317-4232-a648-16206a230d3b-attachment.png", "type": "image/png"}], "start": 1724639382337, "stop": 1724639382337}], "attachments": [{"name": "log", "source": "34a29121-888c-4f9a-b2f0-c05c4e9f661c-attachment.txt", "type": "text/plain"}], "parameters": [{"name": "filename", "value": "'静态文本'"}, {"name": "actual_img_name", "value": "'静态文本_设置右对齐_实际图'"}, {"name": "expect_img_name", "value": "'静态文本_设置右对齐_预期图'"}], "start": 1724639375964, "stop": 1724639382338, "uuid": "f15ce405-3aa0-42f2-859e-fbb09683827d", "historyId": "99685ed6fb73092f5095fb6c1fed81f4", "testCaseId": "c756eeaab2d0948859612d2ccdf0f3d1", "fullName": "TestCases.Test_StaticText.Test_StaticText#test_StaticText_set_right_aligned", "labels": [{"name": "story", "value": "静态文本-设置右对齐"}, {"name": "feature", "value": "静态文本组件"}, {"name": "epic", "value": "CMS2.0UI自动化测试"}, {"name": "parentSuite", "value": "TestCases"}, {"name": "suite", "value": "Test_StaticText"}, {"name": "subSuite", "value": "Test_StaticText"}, {"name": "host", "value": "DESKTOP-V6TD7H4"}, {"name": "thread", "value": "13940-MainThread"}, {"name": "framework", "value": "pytest"}, {"name": "language", "value": "cpython3"}, {"name": "package", "value": "TestCases.Test_StaticText"}]}
\ No newline at end of file
{"uuid": "e2e2b2f9-7890-4430-8f17-ad37fae9b02a", "children": ["f15ce405-3aa0-42f2-859e-fbb09683827d"], "befores": [{"name": "Generate pageobject and video of the operation", "status": "passed", "start": 1724639374993, "stop": 1724639375683}], "afters": [{"name": "Generate pageobject and video of the operation::0", "status": "passed", "attachments": [{"name": "CMS操作的屏幕录制", "source": "90210868-5e1c-4702-bb9a-0d447a96a1c9-attachment.webm", "type": "video/webm"}], "start": 1724639382592, "stop": 1724639382939}], "start": 1724639374993, "stop": 1724639382939}
\ No newline at end of file
[
{
"name": "Ignored tests",
"messageRegex": ".*ignored.*",
"matchedStatuses": ["skipped"]
},
{
"name": "Infrastructure problems",
"messageRegex": ".*RuntimeException.*",
"matchedStatuses": ["broken"]
},
{
"name": "Product defects",
"matchedStatuses": ["failed"]
},
{
"name": "Test defects",
"matchedStatuses": ["broken"]
}
]
\ No newline at end of file
os_platform = mac OS
python_version = Python 3.10
\ No newline at end of file
import yaml
import os
class DubugTalk:
# 获取项目路径
def get_object_path(self):
return os.path.abspath(os.getcwd().split('Utils')[0])
# 读取extract.yml文件
def read_extract_file(self,one_node):
with open(self.get_object_path() + "\\extract.yml", encoding='utf-8') as f:
value = yaml.load(stream=f, Loader=yaml.FullLoader)
return value[one_node]
# 写入extract.yml文件
def write_extract_file(self, data):
with open(self.get_object_path() + "\\extract.yml", encoding='utf-8', mode='a') as f:
yaml.dump(data=data, stream=f, allow_unicode=True)
# 清空extract.yml文件a
def clear_extract_file(self):
with open(self.get_object_path() + "\\extract.yml", encoding='utf-8', mode='w') as f:
f.truncate()
def clear_folder_contents(self, folder_path):
"""
清空指定文件夹下的所有文件(不包括子文件夹)。
"""
# 确保提供的路径确实是一个文件夹
if not os.path.isdir(folder_path):
print(f"Error: {folder_path} is not a directory.")
return
# 遍历文件夹中的所有内容
for filename in os.listdir(folder_path):
# 构建文件的完整路径
file_path = os.path.join(folder_path, filename)
# 检查是否为文件(排除子文件夹)
if os.path.isfile(file_path):
try:
# 删除文件
os.remove(file_path)
except Exception as e:
# 打印错误(可能由于权限问题等)
print(f"Failed to delete {file_path}. Reason: {e}")
# if __name__ == '__main__':
# print(read_config_file('base','base_url'))
# print(get_object_path())
import uuid
class GenerateUUID:
# 生成一个随机的UUID
def get_uuid(self):
return uuid.uuid4()
\ No newline at end of file
# Time : 2024/3/13 15:55
# Author : wangchao
# File : GetObjectPath_Util.py
# Description :
import os
def get_object_path():
# 获取当前工作目录的绝对路径
cwd_path = os.path.abspath(os.getcwd())
return cwd_path.split('Utils')[0]
if __name__ == '__main__':
print(get_object_path())
\ No newline at end of file
# Time : 2024/8/12 11:57
# Author : wangchao
# File : HTTPClient.py
# Description : http工具类
import time
import requests
from requests.exceptions import HTTPError, Timeout, RequestException
class HTTPClient:
def __init__(self, base_url, auth_token=None, headers=None, timeout=10):
self.base_url = base_url
self.auth_token = auth_token
self.headers = headers if headers else {}
self.timeout = timeout
# 如果提供了认证token,添加到请求头
if self.auth_token:
self.headers['Authorization'] = f'Bearer {self.auth_token}'
def get(self, endpoint, params=None):
"""发送GET请求"""
response = requests.get(
f"{self.base_url}/{endpoint}",
params=params,
headers=self.headers,
timeout=self.timeout
)
return self._handle_response(response)
def post(self, endpoint, data):
"""发送POST请求"""
response = requests.post(
f"{self.base_url}/{endpoint}",
data=data,
headers=self.headers,
timeout=self.timeout
)
# 打印请求的详细信息
#print("发送报文请求头:" + str(response.request.headers))
#print("发送报文请求体:" + str(response.request.body))
# 输出响应内容
#print('Post Status Code:', response.status_code)
#print('Post Response Body:', response.text)
return self._handle_response(response)
def put(self, endpoint, data):
"""发送PUT请求"""
response = requests.put(
f"{self.base_url}/{endpoint}",
json=data,
headers=self.headers,
timeout=self.timeout
)
# 输出响应内容
#print('Put Status Code:', response.status_code)
#print('Put Response Body:', response.text)
return self._handle_response(response)
def delete(self, endpoint):
"""发送DELETE请求"""
response = requests.delete(
f"{self.base_url}/{endpoint}",
headers=self.headers,
timeout=self.timeout
)
return self._handle_response(response)
def _handle_response(self, response):
"""处理HTTP响应"""
try:
response.raise_for_status() # 检查请求是否成功
return response.json() if response.content else {} # 返回JSON数据或空字典
except HTTPError as http_err:
print(f"HTTP error occurred: {http_err}") # 提示HTTP错误
except Timeout:
print("The request timed out") # 处理超时
except RequestException as req_err:
print(f"Request error occurred: {req_err}") # 处理其他请求错误
except ValueError:
print("Non-JSON response received") # 处理非JSON响应
return None # 返回None表示出错
# 使用示例
if __name__ == "__main__":
# 创建HTTP客户端
client = HTTPClient(
"http://192.168.1.188:18000",
auth_token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiU3VwZXJBZG1pbiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWVpZGVudGlmaWVyIjoiU3VwZXJBZG1pbiIsImV4cCI6MTcxMDkwMTU0MywiaXNzIjoiQ01TdjIiLCJhdWQiOiJBcGkifQ.ViXzrmAU2txWVQyHkr-dA7wUo9ezntrzdTmJhS9Vv7k", # 替换为你的认证token (如需要)
headers={"Content-Type": "application/x-www-form-urlencoded"}, # 设置请求头
timeout=10 # 设置超时时间为5秒
)
project_name = "demo测试工程" + str(int(time.time()))
# 创建资源
try:
create_response = client.post("api/v1/project/node", {"name": f"{project_name}", "path": "", "type": "1"})
print("Create Response:", create_response)
except Exception as e:
print("Error creating resource:", e)
# # 读取资源
# try:
# read_response = client.get("items", params={"id": 1})
# print("Read Response:", read_response)
# except Exception as e:
# print("Error reading resource:", e)
#
# # 更新资源
# try:
# update_response = client.put("items/1", {"name": "Updated Item", "description": "Updated description"})
# print("Update Response:", update_response)
# except Exception as e:
# print("Error updating resource:", e)
#
# # 删除资源
# try:
# delete_response = client.delete("items/1")
# print("Delete Response:", delete_response)
# except Exception as e:
# print("Error deleting resource:", e)
\ No newline at end of file
# -*- coding:utf-8 -*-
"""
describe:读取测试数据yaml文件
Author:wangchao
Time: 2023/4/17
"""
import yaml, os
from Config import Config
from BuildInLibrary.BuildInLibrary import BuildInLibrary
from Utils.GetObjectPath import get_object_path
class ReadYaml(object):
def __init__(self, filename):
self.filename = filename
# 读取TestDatas目录下的yaml文件
def read(self):
with open(file=self.filename, mode="r", encoding='utf8', ) as f:
bilibrary = BuildInLibrary()
data = f.read()
# 调用内置库的方法替换参数,可以替换多个,满足{{$参数}}规则的参数会被替换
data1 = bilibrary.repalce_parameter(data)
data_yaml = yaml.load(data1, Loader=yaml.FullLoader)
for value in data_yaml:
# 拼接URL地址
if value.get("url地址") is not None:
value["url地址"] = Config.url + value["url地址"]
# 拼接上传文件的地址
if value.get("files") is not None:
value["files"] = Config.test_files_dir + os.path.sep + value["files"]
return data_yaml
# 读取log_config.yml文件
def read_config_file(self, one_node, two_node):
with open(Config.root_dir + os.path.sep + self.filename, encoding='utf-8') as f:
value = yaml.load(stream=f, Loader=yaml.FullLoader)
return value[one_node][two_node]
if __name__ == '__main__':
ry = ReadYaml("log_config.yml")
read = ry.read_config_file('log', 'log_level')
print(read)
# Time : 2024/3/20 21:49
# Author : wangchao
# File : VisualRegression.py
# Description : 引入视觉对比方式,通过图片对比来检测页面是否有变化
from visual_regression_tracker import Config, IgnoreArea
from visual_regression_tracker.playwright import PlaywrightVisualRegressionTracker, PageTrackOptions, \
PageScreenshotOptions, Agent, ElementHandleScreenshotOptions, ElementHandleTrackOptions
def visual_regression(page_obj, browser_type, page_name, element_name, element_locator):
config = Config(
# apiUrl - URL where backend is running(图像对比服务部署在单独的服务器)
apiUrl='http://127.0.0.1:4200',
# project - Project name or ID
project='e34e6e08-86d3-425f-b939-c19894894144',
# apiKey - User apiKey
apiKey='DEFAULTUSERAPIKEYTOBECHANGED',
# ciBuildId - Current git commit SHA
ciBuildId='commit_sha',
# branch - Current git branch
branchName='master',
# enableSoftAssert - Log errors instead of exceptions
enableSoftAssert=True,
)
vrt = PlaywrightVisualRegressionTracker(browser_type, config=config)
with vrt:
vrt.trackPage(page_obj, page_name, PageTrackOptions(
diffTollerancePercent=1.34,
ignoreAreas=[
IgnoreArea(
x=100,
y=200,
width=300,
height=400,
)
],
screenshotOptions=PageScreenshotOptions(
full_page=True,
omit_background=True,
),
agent=Agent(
os='win10 IOT LTSC',
device='装备大屏',
)
))
vrt.trackElementHandle(element_locator, element_name, ElementHandleTrackOptions(
diffTollerancePercent=1.34,
ignoreAreas=[
IgnoreArea(
x=1,
y=2,
width=30,
height=10,
)
],
screenshotOptions=ElementHandleScreenshotOptions(
omit_background=True,
),
agent=Agent(
os='win10 IOT LTSC',
device='装备大屏',
viewport='1200x12'
)
))
import logging
import os
import time
import datetime
from Utils.GetObjectPath import get_object_path
from Utils.ReadYaml import ReadYaml
from Config import Config
class LoggerUtil:
def creat_log(self, logger_name='log'):
yml_data = ReadYaml("log_config.yml")
# 创建一个日志对象
self.logger = logging.getLogger(logger_name)
# 设置全局的日志级别(critical>error>waring>info>debug)
self.logger.setLevel(logging.DEBUG)
# 防止日志重复
if not self.logger.handlers:
# -----------文件日志----------
# 获得日志文件的名称
log_name = Config.log_name
log_level = Config.log_level
log_format = Config.log_format
self.file_log_path = Config.root_dir + os.path.sep + 'Logs' + os.sep + log_name + datetime.datetime.now().strftime("%Y%m%d") + "_" + str(int(time.time())) + '.log'
# print(self.file_log_path)
# 创建文件日志的控制器
self.file_hander = logging.FileHandler(self.file_log_path, encoding='utf-8')
# 设置文件日志的界别
file_log_lever = str(log_level).lower()
if file_log_lever == 'debug':
self.file_hander.setLevel(logging.DEBUG)
elif file_log_lever == 'info':
self.file_hander.setLevel(logging.INFO)
elif file_log_lever == 'warning':
self.file_hander.setLevel(logging.WARNING)
elif file_log_lever == 'error':
self.file_hander.setLevel(logging.ERROR)
elif file_log_lever == 'critical':
self.file_hander.setLevel(logging.CRITICAL)
# 设置文件日志的格式
self.file_hander.setFormatter(logging.Formatter(log_format))
# 将控制器加入到日志对象
self.logger.addHandler(self.file_hander)
# -----------控制台日志----------
# 创建控制台日志的控制器
self.console_hander = logging.StreamHandler()
#控制台日志格式化
console_log_format = log_format
# 设置控制台日志的界别
console_log_lever = str(log_level).lower()
if console_log_lever == 'debug':
self.console_hander.setLevel(logging.DEBUG)
elif console_log_lever == 'info':
self.file_hander.setLevel(logging.INFO)
elif console_log_lever == 'warning':
self.console_hander.setLevel(logging.WARNING)
elif console_log_lever == 'error':
self.console_hander.setLevel(logging.ERROR)
elif console_log_lever == 'critical':
self.console_hander.setLevel(logging.CRITICAL)
# 设置控制台日志的格式
self.console_hander.setFormatter(logging.Formatter(console_log_format))
# 将控制器加入到日志对象
self.logger.addHandler(self.console_hander)
return self.logger
# 函数:输出正常日志
def write_log(log_message):
LoggerUtil().creat_log().info(log_message)
# 函数:输出错误日志
def error_log(log_message):
LoggerUtil().creat_log().error(log_message)
raise Exception(log_message)
if __name__ == '__main__':
write_log("正常日志")
write_log("正常日志")
# error_log("错误日志")
\ No newline at end of file
import json
import os
import time
import allure
import pytest
import requests
from playwright.sync_api import sync_playwright, Playwright, APIRequestContext
import datetime
from BuildInLibrary.BuildInLibrary import BuildInLibrary
from Config import Config
from Utils.HTTPClient import HTTPClient
from Utils.Debug_talk import DubugTalk
from Utils.logger_util import write_log, error_log
import sys
@pytest.fixture(scope="function")
@allure.title("Generate pageobject and video of the operation")
def page():
"""
功能:在每个用例执行之前,初始化page对象,并在用例执行完成后生成页面操作视频,作为附件添加到allure报告中
返回:page对象
"""
browser = None
context = None
page = None
try:
with sync_playwright() as p:
browser = p.chromium.launch(headless=False, channel=Config.browser,
args=['--start-maximized']) # 设置headless为False以显示浏览器界面
# 设置视频录制目录(record_video_dir)、视频分辨率(record_video_size)、视频窗口大小(no_viewport)
context = browser.new_context(no_viewport=True, locale="zh-CN",
record_video_size={"width": 1920, "height": 1080}, record_video_dir="./TestReport/Videos/",
accept_downloads=True)
page = context.new_page()
write_log("\n-------------测试用例开始-----------")
yield page # 返回page对象
write_log("\n-------------测试用例结束-----------")
context.close()
browser.close()
# 将视频文件作为附件添加到Allure报告
video_path = page.video.path() # 获取视频路径
# print("打印视频路径:",video_path)
if os.path.exists(video_path):
try:
allure.attach(body=open(video_path, "rb").read(),
attachment_type=allure.attachment_type.WEBM,
name="CMS操作的屏幕录制",)
except Exception as e:
error_log(f"page fixture log:添加allure视频附件出现异常: {e}")
except Exception as e:
error_log(f"page fixture log:fixture log:page fixture执行过程出现异常: {e}")
@pytest.fixture(scope="class")
def global_build_in_library_object():
"""
功能:在每个测试类运行之前,初始化内置库全局对象
返回:内置库实例
"""
# 在这里初始化内置库全局对象
bl = BuildInLibrary()
yield bl
# @pytest.hookimpl(tryfirst=True, hookwrapper=True)
# def pytest_runtest_makereport(item, call):
# """
# 功能:获取每个用例状态的钩子函数
# 参数:
# item: 测试用例
# call: 测试步骤
# """
# # 获取钩子方法的调用结果
# out_come = yield
# rep = out_come.get_result() # 从钩子方法的调用结果中获取测试报告
# # rep.when表示测试步骤,仅仅获取用例call执行结果是失败的情况,不包含setup/teardown
# if rep.when == "call" and rep.failed:
# failure_log_file_name = "failure_log_" + datetime.datetime.now().strftime("%Y%m%d") + "_" + str(int(time.time())) +".log"
# failures_log = os.path.join(Config.logs, failure_log_file_name)
# mode = "a" if os.path.exists(failures_log) else "w"
# with open(failures_log, mode) as f:
# # let's also access a fixture for the fun of it
# if "CaseData" in item.fixturenames:
# extra = " (%s)" % item.funcargs["CaseData"]
# else:
# extra = ""
# f.write(f"{datetime.datetime.now()}" + " " + rep.nodeid + extra + "\n")
# pageobject = item.funcargs.get('page') # 获取page() fixture 返回值(页面对象)
# # 添加allure报告失败截图
# if hasattr(pageobject, "screenshot"):
# with allure.step('添加用例执行失败截图...'):
# path = Config.test_screenshot_dir + os.path.sep + "失败截图.png"
# # 按指定区域截图
# screenshot_area = {'x': Config.x_coordinate, 'y': Config.y_coordinate, 'width': Config.width,'height': Config.height}
# file = pageobject.screenshot(path=path, full_page=True, clip=screenshot_area)
# # file = pageobject.screenshot(path=path)
# allure.attach(file, "失败截图", allure.attachment_type.PNG)
# write_log("pytest_runtest_makereport log:添加失败截图成功")
# else:
# error_log("pytest_runtest_makereport log:添加失败截图失败")
@pytest.fixture(scope="session", autouse=True) #session
@allure.title("创建工程")
def project_init():
"""
功能:用例执行前创建空白测试工程并初始化,用例执行完毕删除测试工程
返回:工程ID
"""
# 清空extract.yaml文件
DubugTalk().clear_extract_file()
# 清空videos路径下的所有文件
videos_path = Config.test_video_dir
DubugTalk().clear_folder_contents(videos_path)
# 清空Logs路径下的所有文件
logs_path = Config.logs
DubugTalk().clear_folder_contents(logs_path)
"""创建空白测试工程"""
url = Config.url+r"/api/v1/project/node"
project_name = f"UI自动化测试项目_{str(int(time.time()))}"
data = {"name": f"{project_name}", "path": None, "type": "1"}
try:
response = requests.post(url=url,data=data)
if response.status_code!=200:
error_log(f"工程创建失败,状态码为{response.status_code}")
else:
data = {"project_id": response.text}
# 写入extract.yml文件
DubugTalk().write_extract_file(data)
except Exception as e:
error_log(f"工程创建失败:{e}")
"""获取工程id X_project"""
project_id = DubugTalk().read_extract_file("project_id")
url = Config.url + rf"/api/v1/project/{project_id}/info"
try:
response = requests.get(url=url)
if response.status_code != 200:
error_log(f"获取工程信息失败,状态码为{response.status_code}")
else:
data = {"x_project": response.json()["identifier"]}
# 写入extract.yml文件
DubugTalk().write_extract_file(data)
except Exception as e:
error_log(f"获取工程信息失败:{e}")
@pytest.fixture(scope="session", autouse=True)
def upload_image():
"""
在图库中上传图片,图片名为“皮卡丘.jpg”
:return:
"""
url = Config.url+r"/api/v1/view/image/gallery?group=我的\Default"
x_project = DubugTalk().read_extract_file("x_project")
headers= {'X-Project': f"{x_project}"}
file_path = Config.test_project_dir + r"\Gallery\皮卡丘.jpg"
#文件数据
files = {'formFiles': open(file_path,'rb')}
try:
re = requests.post(url=url, headers=headers, files=files)
if re.status_code != 200:
error_log(f"上传图片失败,状态码为{re.status_code}")
except Exception as e:
error_log(f"上传图片失败:{e}")
@pytest.fixture(scope="function", autouse=False)
def create_and_destroy_page():
"""
测试用例执行前创建画面,执行结束删除画面
:return:
"""
# 新建画面
response = None
newpage_url = Config.url+r"/api/v1/view/node?copyFrom="
x_project = DubugTalk().read_extract_file("x_project")
headers = {'X-Project': f"{x_project}"}
PageName = "画面" + str(int(time.time()))
data = {"name": f"{PageName}", "parentId": None, "type": 1, "sort": 1}
try:
response = requests.post(url=newpage_url, headers=headers, json=data)
if response.status_code != 200:
error_log(f"画面创建失败,状态码为{response.status_code}")
except Exception as e:
error_log(f"页面创建失败:{e}")
yield
# 删除画面
delpage_url = Config.url + r"/api/v1/view/node/"+response.text
x_project = DubugTalk().read_extract_file("x_project")
headers = {'X-Project': f"{x_project}"}
try:
re = requests.delete(url=delpage_url, headers=headers)
if re.status_code != 200:
error_log(f"画面删除失败,状态码为{re.status_code}")
except Exception as e:
error_log(f"页面删除失败:{e}")
project_id: '77'
x_project: c020397cbc814ec98c7dd1cd5a195cc6
[pytest]
# 配置pytest命令行运行参数,空格分隔,可添加多个命令行参数,所有参数均为插件包的参数(reruns:用例执行失败重试次数,tracing:开启playwright的trace记录,成功则删除)
addopts = -v -s --reruns=0 -p pytest_playwright --tracing=retain-on-failure --alluredir ./TestReport/AllureResult --clean-alluredir
# 配置是否在控制台输出日志
log_cli = 0
# 配置控制台日志级别(DEBUG、INFO、WARNING、ERROR)
log_cli_level = INFO
# 自定义命令行日志的输出格式,可以定义自己的日志格式,也可以使用默认的格式
log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
# 自定义命令行日志的日期格式
log_cli_date_format = %Y-%m-%d %H:%M:%S
# 配置测试搜索的路径,当前目录下的TestCases文件夹,可自定义
testpaths = ./TestCases
# 配置测试搜索的文件名,当前目录下的Testcase文件夹下,以Test_开头,以.py结尾的所有文件,可自定义
python_files = Test*.py
# 配置测试搜索的测试类名,当前目录下的Testcase文件夹下,以Test_开头,以.py结尾的所有文件中,以Test开头的类,可自定义
python_classes = Test*
# 配置测试搜索的测试函数名,当前目录下的Testcase文件夹下,以Test开头,以.py结尾的所有文件中,以Test_开头的类内,以test_开头的方法,可自定义
python_functions = test*
# 定义测试用例的标记,可以在测试用例上使用这些标记来筛选或分类测试。注册标记后,可以使用 @pytest.mark.marker_name 装饰器将标记应用到测试用例、测试类或模块上
markers =
smoking: 冒烟用例
regression: 回归用例
allure-pytest==2.13.5
allure-python-commons==2.13.5
attrs==23.2.0
certifi==2024.7.4
charset-normalizer==3.3.2
dacite==1.8.1
exceptiongroup==1.2.2
execnet==2.1.1
greenlet==3.0.3
idna==3.7
iniconfig==2.0.0
MouseInfo==0.1.3
numpy==2.0.1
opencv-python==4.10.0.84
packaging==24.1
pillow==10.4.0
playwright==1.45.1
pluggy==1.5.0
PyAutoGUI==0.9.54
pyee==11.1.0
PyGetWindow==0.0.9
PyMsgBox==1.0.9
pynput==1.7.7
pyperclip==1.9.0
PyRect==0.2.0
PyScreeze==0.1.30
pytest==8.3.1
pytest-base-url==2.1.0
pytest-dependency==0.6.0
pytest-ordering==0.6
pytest-playwright==0.5.1
pytest-rerunfailures==14.0
pytest-sugar==1.0.0
pytest-xdist==3.6.1
python-slugify==8.0.4
pytweening==1.2.0
PyYAML==6.0.1
requests==2.32.3
rubicon-objc==0.4.9
six==1.16.0
termcolor==2.4.0
text-unidecode==1.3
tomli==2.0.1
typing_extensions==4.12.2
urllib3==2.2.2
visual-regression-tracker==4.9.0
Pillow
import os
import time
import pytest
from Config import Config
if __name__ == '__main__':
AllureReport = Config.test_report_dir
AllureResult = Config.test_result_dir
TestReport = Config.test_report_parent_dir
pytest.main()
time.sleep(1)
# 根据操作系统不同,将allure环境配置文件和分类文件拷贝到AllureResult报告目录下
if os.name == 'nt':
os.system(f"xcopy {os.path.join(TestReport, 'environment.properties')} {AllureReport} /E /I")
os.system(f"xcopy {os.path.join(TestReport, 'categories.json')} {AllureReport} /E /I")
else:
os.system(f"cp -r {os.path.join(TestReport, 'environment.properties')} {AllureResult}")
os.system(f"cp -r {os.path.join(TestReport, 'categories.json')} {AllureResult}")
# # 产生allure报告
os.system('allure generate ./TestReport/AllureResult -o ./TestReport/AllureReport --clean')
# # 打开allure报告
# os.system(f'allure open {AllureReport}')
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment