Commit 03c8c945 by linguangwei

同步

parent 2d1e30f0
......@@ -2,139 +2,16 @@
<project version="4">
<component name="ChangeListManager">
<list default="true" id="f091fe1a-072c-4ac2-a8ca-080be739597a" name="Default Changelist" comment="">
<change afterPath="$PROJECT_DIR$/case/__init__.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/case/test_enter.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/commom/__init__.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/liveRoom/__init__.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/liveRoom/coursewareTab.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/liveRoom/roomName.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/liveRoom/startPrompt.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/xm-uitest-live.iml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/xm-uitest-live.iml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/alertAccept.py" beforeDir="false" afterPath="$PROJECT_DIR$/commom/alertAccept.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/appDriver.py" beforeDir="false" afterPath="$PROJECT_DIR$/commom/appDriver.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/liveCourseTable/classStatus.py" beforeDir="false" afterPath="$PROJECT_DIR$/liveCourseTable/classStatus.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/liveCourseTable/enterClass.py" beforeDir="false" afterPath="$PROJECT_DIR$/liveCourseTable/enterClass.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/commom/appDriver.py" beforeDir="false" afterPath="$PROJECT_DIR$/commom/appDriver.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/liveCourseTable/startClass.py" beforeDir="false" afterPath="$PROJECT_DIR$/liveCourseTable/startClass.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/liveCourseTable/today.py" beforeDir="false" afterPath="$PROJECT_DIR$/liveCourseTable/today.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/liveCourseTable/weekDay.py" beforeDir="false" afterPath="$PROJECT_DIR$/liveCourseTable/weekDay.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/liveCourseTable/weekName.py" beforeDir="false" afterPath="$PROJECT_DIR$/liveCourseTable/weekName.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/login.py" beforeDir="false" afterPath="$PROJECT_DIR$/commom/login.py" afterDir="false" />
</list>
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<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="FileEditorManager">
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/liveCourseTable/classStatus.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="220">
<caret line="11" column="54" selection-start-line="11" selection-start-column="54" selection-end-line="11" selection-end-column="54" />
<folding>
<element signature="e#0#36#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/commom/appDriver.py">
<provider selected="true" editor-type-id="text-editor">
<state>
<caret line="5" column="9" selection-start-line="5" selection-start-column="9" selection-end-line="5" selection-end-column="9" />
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/liveCourseTable/enterClass.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="240">
<caret line="12" column="60" selection-start-line="12" selection-start-column="60" selection-end-line="12" selection-end-column="60" />
<folding>
<element signature="e#0#36#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/case/test_enter.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="120">
<caret line="6" column="11" selection-start-line="6" selection-start-column="11" selection-end-line="6" selection-end-column="11" />
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/commom/__init__.py">
<provider selected="true" editor-type-id="text-editor" />
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/commom/login.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="140">
<caret line="7" column="56" selection-start-line="7" selection-start-column="56" selection-end-line="7" selection-end-column="56" />
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/liveRoom/roomName.py">
<provider selected="true" editor-type-id="text-editor">
<state>
<caret selection-end-line="20" selection-end-column="20" />
<folding>
<element signature="e#0#36#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/liveRoom/coursewareTab.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="400">
<caret line="20" column="13" selection-start-line="20" selection-start-column="13" selection-end-line="20" selection-end-column="13" />
<folding>
<element signature="e#0#36#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/liveRoom/startPrompt.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="140">
<caret line="7" column="21" selection-start-line="7" selection-start-column="21" selection-end-line="7" selection-end-column="21" />
<folding>
<element signature="e#0#36#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/liveCourseTable/startClass.py">
<provider selected="true" editor-type-id="text-editor">
<state>
<folding>
<element signature="e#0#36#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
......@@ -146,6 +23,14 @@
<option name="UPDATE_TYPE" value="MERGE" />
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="GitSEFilterConfiguration">
<file-type-list>
<filtered-out-file-type name="LOCAL_BRANCH" />
<filtered-out-file-type name="REMOTE_BRANCH" />
<filtered-out-file-type name="TAG" />
<filtered-out-file-type name="COMMIT_BY_MESSAGE" />
</file-type-list>
</component>
<component name="IdeDocumentHistory">
<option name="CHANGED_PATHS">
<list>
......@@ -171,6 +56,7 @@
<option name="width" value="1874" />
<option name="height" value="1189" />
</component>
<component name="ProjectId" id="1tNJXUBIdF7Iljvh6v3JNiXndy2" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectView">
<navigator proportions="" version="1">
......@@ -196,6 +82,10 @@
<pane id="Scope" />
</panes>
</component>
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="SHARE_PROJECT_CONFIGURATION_FILES" value="true" />
<property name="WebServerToolWindowFactoryState" value="false" />
......@@ -216,20 +106,8 @@
<recent name="C:\Users\Administrator\PycharmProjects\xm-uitest-live" />
</key>
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="RunManager" selected="Python tests.pytest for test_enter.test_answer">
<configuration name="classStatus" type="PythonConfigurationType" factoryName="Python" temporary="true">
<component name="RunManager" selected="Python.main">
<configuration name="appDriver" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="xm-uitest-live" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
......@@ -237,12 +115,11 @@
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/liveCourseTable" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/commom" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/liveCourseTable/classStatus.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/commom/appDriver.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
......@@ -251,7 +128,7 @@
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="enterClass" type="PythonConfigurationType" factoryName="Python" temporary="true">
<configuration name="demo" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="xm-uitest-live" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
......@@ -259,12 +136,11 @@
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/liveCourseTable" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/framework" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/liveCourseTable/enterClass.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/framework/demo.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
......@@ -273,7 +149,7 @@
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="login" type="PythonConfigurationType" factoryName="Python" temporary="true">
<configuration name="main" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="xm-uitest-live" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
......@@ -285,8 +161,7 @@
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="D:\pycharm projects\xm-uitest-sow\commom\login.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/main.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
......@@ -295,54 +170,49 @@
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="roomName" type="PythonConfigurationType" factoryName="Python" temporary="true">
<configuration name="pytest for testLoginPage.TestLoginPage.testLogin" type="tests" factoryName="py.test" temporary="true" nameIsGenerated="true">
<module name="xm-uitest-live" />
<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$/liveRoom" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/testcase" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/liveRoom/roomName.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="" />
<option name="_new_keywords" value="&quot;&quot;" />
<option name="_new_parameters" value="&quot;&quot;" />
<option name="_new_additionalArguments" value="&quot;&quot;" />
<option name="_new_target" value="&quot;testLoginPage.TestLoginPage.testLogin&quot;" />
<option name="_new_targetType" value="&quot;PYTHON&quot;" />
<method v="2" />
</configuration>
<configuration name="pytest for test_enter.test_answer" type="tests" factoryName="py.test" temporary="true" nameIsGenerated="true">
<configuration name="pytest in testLoginPage.py" type="tests" factoryName="py.test" temporary="true" nameIsGenerated="true">
<module name="xm-uitest-live" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/case" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/testcase" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="_new_keywords" value="&quot;&quot;" />
<option name="_new_parameters" value="&quot;&quot;" />
<option name="_new_additionalArguments" value="&quot;&quot;" />
<option name="_new_target" value="&quot;test_enter.test_answer&quot;" />
<option name="_new_targetType" value="&quot;PYTHON&quot;" />
<option name="_new_target" value="&quot;$PROJECT_DIR$/src/testcase/testLoginPage.py&quot;" />
<option name="_new_targetType" value="&quot;PATH&quot;" />
<method v="2" />
</configuration>
<recent_temporary>
<list>
<item itemvalue="Python tests.pytest for test_enter.test_answer" />
<item itemvalue="Python.enterClass" />
<item itemvalue="Python.roomName" />
<item itemvalue="Python.classStatus" />
<item itemvalue="Python.login" />
<item itemvalue="Python.main" />
<item itemvalue="Python tests.pytest in testLoginPage.py" />
<item itemvalue="Python.demo" />
<item itemvalue="Python tests.pytest for testLoginPage.TestLoginPage.testLogin" />
<item itemvalue="Python.appDriver" />
</list>
</recent_temporary>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="SvnConfiguration">
<configuration />
</component>
......@@ -436,20 +306,23 @@
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" />
</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="test test" />
<MESSAGE value="just just test" />
<MESSAGE value="rename" />
<option name="LAST_COMMIT_MESSAGE" value="rename" />
</component>
<component name="com.intellij.coverage.CoverageDataManagerImpl">
<SUITE FILE_PATH="coverage/xm_uitest_sow$enterClass.coverage" NAME="enterClass Coverage Results" MODIFIED="1621568267725" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/liveCourseTable" />
<SUITE FILE_PATH="coverage/xm_uitest_live$wyfTest.coverage" NAME="wyfTest Coverage Results" MODIFIED="1620809942975" 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/xm_uitest_sow$classStatus.coverage" NAME="classStatus Coverage Results" MODIFIED="1620974730032" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/liveCourseTable" />
<SUITE FILE_PATH="coverage/xm_uitest_sow$roomName.coverage" NAME="roomName Coverage Results" MODIFIED="1621567390821" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/liveRoom" />
<SUITE FILE_PATH="coverage/xm_uitest_sow$login.coverage" NAME="login Coverage Results" MODIFIED="1620884769813" 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/xm_uitest_sow$pytest_for_test_enter_test_answer.coverage" NAME="pytest for test_enter.test_answer Coverage Results" MODIFIED="1621586260062" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/case" />
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/server.log" />
<entry file="file://$PROJECT_DIR$/wyfTest.py" />
......
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.8 (xm-uitest-sow)" jdkType="Python SDK" />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TestRunnerService">
<option name="projectConfiguration" value="pytest" />
<option name="PROJECT_TEST_RUNNER" value="pytest" />
</component>
</module>
\ No newline at end of file
import os
from time import sleep, time
from src.framework.common import file_abspath
from selenium import webdriver
from selenium.webdriver.chrome import service
from selenium.webdriver.chrome.webdriver import WebDriver
# web_driver_path = os.path.join(
# "./chromedriver")
service = webdriver.chrome.service.Service(r"C:\Users\Administrator\AppData\Local\Programs\Python\Python38-32\scripts\chromedriver.exe")
service = webdriver.chrome.service.Service(file_abspath() + "/tools/chromedriver.exe")
service.start()
......
#暂时只用Chrome
[browserType]
#browserName = Firefox
browserName = Chrome
#browserName = IE
[testServer]
#输入你要测得本地文件路径
app_location = C:\Users\Administrator\AppData\Local\Programs\xmqxy\小麦企学院.exe
[account]
username = 13777867342
password = 0000
[MySql]
[chrome download]
url = http://chromedriver.storage.googleapis.com/
\ No newline at end of file
import configparser
import os
config = configparser.ConfigParser()
dir = os.path.abspath('.').split('src')[0]
config.read(dir + "/config/config.ini", encoding='utf-8')
# 根据配置文件获取测试url
def test_location():
location = config.get("testServer", "app_location")
return location
def test_account():
return config.get("account", "username")
def test_password():
return config.get("account", "password")
\ No newline at end of file
......@@ -8,6 +8,7 @@ app_login(driver)
# 进入直播间
def start_class(driver):
sleep(3)
......@@ -18,5 +19,6 @@ def start_class(driver):
return start_class
if __name__ == '__main__':
start_class(driver)
import unittest
from src.framework.HTMLTestRunner import HTMLTestRunner
from src.framework import common
from src.testcase.testLoginPage import TestLoginPage as TLP
# from src.testcase.testHomePage import TestHomePage as THP
# from src.testcase.testWordPage import TestWordPage as TWP
if __name__ == '__main__':
testunit = unittest.TestSuite()
testunit.addTest(TLP('testLogin'))
# testunit.addTests([THP('testHonors'), THP('testModifyPassword'), THP('testModifyInformation'), THP('testLogout'), THP('testWordTest')])
# testunit.addTests([TWP('testWordsEvaluation'), TWP('testWordReinforcement'), TWP('test_unit'), TWP('testOfflineDictation'), TWP('testPreview'), TWP('testShorthand'), TWP('testDictation'), TWP('testMemoryWrite'), TWP('testPostTest'), TWP('testReview'), TWP('testxiaoxiaole')])
# 定义报告输出路径
htmlPath = "./report/AI-XM_Report{}.html".format(common.year_to_minute())
with(open(htmlPath, 'wb')) as fp:
runner = HTMLTestRunner(
stream=fp,
title='AI-小麦测试报告',
description='描述'
)
runner.run(testunit)
<?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.8 (framework)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ 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="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (framework)" 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/framework.iml" filepath="$PROJECT_DIR$/.idea/framework.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
</component>
</project>
\ No newline at end of file
"""
A TestRunner for use with the Python unit testing framework. It
generates a HTML report to show the result at a glance.
The simplest way to use this is to invoke its main method. E.g.
import unittest
import HTMLTestRunner
... define your tests ...
if __name__ == '__main__':
HTMLTestRunner.main()
For more customization options, instantiates a HTMLTestRunner object.
HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.
# output to a file
fp = file('my_report.html', 'wb')
runner = HTMLTestRunner.HTMLTestRunner(
stream=fp,
title='My unit test',
description='This demonstrates the report output by HTMLTestRunner.'
)
# Use an external stylesheet.
# See the Template_mixin class for more customizable options
runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'
# run the test
runner.run(my_test_suite)
------------------------------------------------------------------------
Copyright (c) 2004-2007, Wai Yip Tung
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name Wai Yip Tung nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
# URL: http://tungwaiyip.info/software/HTMLTestRunner.html
__author__ = "Wai Yip Tung , bugmaster"
__version__ = "0.9.0"
"""
Change History
Version 0.9.0
* Increased repeat execution
* Added failure screenshots
Version 0.8.2
* Show output inline instead of popup window (Viorel Lupu).
Version in 0.8.1
* Validated XHTML (Wolfgang Borgert).
* Added description of test classes and test cases.
Version in 0.8.0
* Define Template_mixin class for customization.
* Workaround a IE 6 bug that it does not treat <script> block as CDATA.
Version in 0.7.1
* Back port to Python 2.3 (Frank Horowitz).
* Fix missing scroll bars in detail log (Podi).
"""
# TODO: color stderr
# TODO: simplify javascript using ,ore than 1 class in the class attribute?
import datetime
import io
import sys
import time
import copy
import unittest
from xml.sax import saxutils
# ------------------------------------------------------------------------
# The redirectors below are used to capture output during testing. Output
# sent to sys.stdout and sys.stderr are automatically captured. However
# in some cases sys.stdout is already cached before HTMLTestRunner is
# invoked (e.g. calling logging.basicConfig). In order to capture those
# output, use the redirectors for the cached stream.
#
# e.g.
# >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)
# >>>
class OutputRedirector(object):
""" Wrapper to redirect stdout or stderr """
def __init__(self, fp):
self.fp = fp
def write(self, s):
self.fp.write(s)
def writelines(self, lines):
self.fp.writelines(lines)
def flush(self):
self.fp.flush()
stdout_redirector = OutputRedirector(sys.stdout)
stderr_redirector = OutputRedirector(sys.stderr)
# ----------------------------------------------------------------------
# Template
class Template_mixin(object):
"""
Define a HTML template for report customerization and generation.
Overall structure of an HTML report
HTML
+------------------------+
|<html> |
| <head> |
| |
| STYLESHEET |
| +----------------+ |
| | | |
| +----------------+ |
| |
| </head> |
| |
| <body> |
| |
| HEADING |
| +----------------+ |
| | | |
| +----------------+ |
| |
| REPORT |
| +----------------+ |
| | | |
| +----------------+ |
| |
| ENDING |
| +----------------+ |
| | | |
| +----------------+ |
| |
| </body> |
|</html> |
+------------------------+
"""
STATUS = {
0: 'pass',
1: 'fail',
2: 'error',
3: 'skip',
}
DEFAULT_TITLE = 'Unit Test Report'
DEFAULT_DESCRIPTION = ''
# ------------------------------------------------------------------------
# HTML Template
HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>%(title)s</title>
<meta name="generator" content="%(generator)s"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script>
<script src="http://apps.bdimg.com/libs/Chart.js/0.2.0/Chart.min.js"></script>
<link rel="stylesheet" href="http://img.itest.info/seldom.css">
%(stylesheet)s
</head>
<body>
<script language="javascript" type="text/javascript">
function show_img(obj) {
var obj1 = obj.nextElementSibling
obj1.style.display='block'
var index = 0;//每张图片的下标,
var len = obj1.getElementsByTagName('img').length;
var imgyuan = obj1.getElementsByClassName('imgyuan')[0]
//var start=setInterval(autoPlay,500);
obj1.onmouseover=function(){//当鼠标光标停在图片上,则停止轮播
clearInterval(start);
}
obj1.onmouseout=function(){//当鼠标光标停在图片上,则开始轮播
start=setInterval(autoPlay,1000);
}
for (var i = 0; i < len; i++) {
var font = document.createElement('font')
imgyuan.appendChild(font)
}
var lis = obj1.getElementsByTagName('font');//得到所有圆圈
changeImg(0)
var funny = function (i) {
lis[i].onmouseover = function () {
index=i
changeImg(i)
}
}
for (var i = 0; i < lis.length; i++) {
funny(i);
}
function autoPlay(){
if(index>len-1){
index=0;
clearInterval(start); //运行一轮后停止
}
changeImg(index++);
}
imgyuan.style.width= 25*len +"px";
//对应圆圈和图片同步
function changeImg(index) {
var list = obj1.getElementsByTagName('img');
var list1 = obj1.getElementsByTagName('font');
for (i = 0; i < list.length; i++) {
list[i].style.display = 'none';
list1[i].style.backgroundColor = 'white';
}
list[index].style.display = 'block';
list1[index].style.backgroundColor = 'red';
}
}
function hide_img(obj){
obj.parentElement.style.display = "none";
obj.parentElement.getElementsByClassName('imgyuan')[0].innerHTML = "";
}
output_list = Array();
/* level - 0:Summary; 1:Failed; 2:Skip; 3:All */
function showCase(level, channel) {
trs = document.getElementsByTagName("tr");
for (var i = 0; i < trs.length; i++) {
tr = trs[i];
id = tr.id;
if (["ft","pt","et","st"].indexOf(id.substr(0,2))!=-1){
if ( level == 0 && id.substr(2,1) == channel ) {
tr.className = 'hiddenRow';
}
}
if (id.substr(0,3) == 'pt'+ channel) {
if ( level == 1){
tr.className = '';
}
else if (level > 4 && id.substr(2,1) == channel ){
tr.className = '';
}
else {
tr.className = 'hiddenRow';
}
}
if (id.substr(0,3) == 'ft'+channel) {
if (level == 2) {
tr.className = '';
}
else if (level > 4 && id.substr(2,1) == channel ){
tr.className = '';
}
else {
tr.className = 'hiddenRow';
}
}
if (id.substr(0,3) == 'et'+channel) {
if (level == 3) {
tr.className = '';
}
else if (level > 4 && id.substr(2,1) == channel ){
tr.className = '';
}
else {
tr.className = 'hiddenRow';
}
}
if (id.substr(0,3) == 'st'+channel) {
if (level == 4) {
tr.className = '';
}
else if (level > 4 && id.substr(2,1) == channel ){
tr.className = '';
}
else {
tr.className = 'hiddenRow';
}
}
}
}
function showClassDetail(cid, count) {
var id_list = Array(count);
var toHide = 1;
for (var i = 0; i < count; i++) {
tid0 = 't' + cid.substr(1) + '.' + (i+1);
tid = 'f' + tid0;
tr = document.getElementById(tid);
if (!tr) {
tid = 'p' + tid0;
tr = document.getElementById(tid);
}
if (!tr) {
tid = 'e' + tid0;
tr = document.getElementById(tid);
}
if (!tr) {
tid = 's' + tid0;
tr = document.getElementById(tid);
}
id_list[i] = tid;
if (tr.className) {
toHide = 0;
}
}
for (var i = 0; i < count; i++) {
tid = id_list[i];
if (toHide) {
document.getElementById(tid).className = 'hiddenRow';
}
else {
document.getElementById(tid).className = '';
}
}
}
function showTestDetail(div_id){
var details_div = document.getElementById(div_id)
var displayState = details_div.style.display
// alert(displayState)
if (displayState != 'block' ) {
displayState = 'block'
details_div.style.display = 'block'
}
else {
details_div.style.display = 'none'
}
}
function html_escape(s) {
s = s.replace(/&/g,'&amp;');
s = s.replace(/</g,'&lt;');
s = s.replace(/>/g,'&gt;');
return s;
}
/* obsoleted by detail in <div>
function showOutput(id, name) {
var w = window.open("", //url
name,
"resizable,scrollbars,status,width=800,height=450");
d = w.document;
d.write("<pre>");
d.write(html_escape(output_list[id]));
d.write("\n");
d.write("<a href='javascript:window.close()'>close</a>\n");
d.write("</pre>\n");
d.close();
}
*/
</script>
%(heading)s
%(report)s
%(ending)s
%(chart_script)s
</body>
</html>
"""
# variables: (title, generator, stylesheet, heading, report, ending)
# ------------------------------------------------------------------------
# Stylesheet
#
# alternatively use a <link> for external style sheet, e.g.
# <link rel="stylesheet" href="$url" type="text/css">
STYLESHEET_TMPL = """
<style type="text/css" media="screen">
body { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; }
table { font-size: 100%; }
pre { }
/* -- heading ---------------------------------------------------------------------- */
h1 {
font-size: 16pt;
color: gray;
}
.heading {
margin-top: 20px;
margin-bottom: 1ex;
margin-left: 10px;
margin-right: 10px;
width: 23%;
float: left;
padding-top: 10px;
padding-left: 10px;
padding-bottom: 10px;
padding-right: 10px;
box-shadow:0px 0px 5px #000;
}
.heading .attribute {
margin-top: 1ex;
margin-bottom: 0;
}
.heading .description {
margin-top: 4ex;
margin-bottom: 6ex;
}
/* -- css div popup ------------------------------------------------------------------------ */
a.popup_link {
}
a.popup_link:hover {
color: red;
}
.popup_window {
display: none;
position: relative;
left: 0px;
top: 0px;
/*border: solid #627173 1px; */
font-family: "Lucida Console", "Courier New", Courier, monospace;
text-align: left;
font-size: 12pt;
width: 500px;
}
}
/* -- report ------------------------------------------------------------------------ */
#show_detail_line {
margin-top: 3ex;
margin-bottom: 1ex;
margin-left: 10px;
}
#header_row {
font-weight: bold;
color: #606060;
background-color: #f5f5f5;
border-top-width: 10px;
border-color: #d6e9c6;
font-size: 15px;
}
#total_row { font-weight: bold; background-color: #dee2e6;}
.passClass { background-color: #d6e9c6; }
.failClass { background-color: #faebcc; }
.errorClass { background-color: #ebccd1; }
.passCase { color: #28a745; font-weight: bold;}
.failCase { color: #c60; font-weight: bold; }
.errorCase { color: #c00; font-weight: bold; }
.hiddenRow { display: none; }
.none {color: #009900 }
.testcase { margin-left: 2em; }
/* -- ending ---------------------------------------------------------------------- */
#ending {
}
/* -- chars ---------------------------------------------------------------------- */
.testChars {width: 900px;margin-left: 0px;}
.error-color {
color: #fff;
background-color: #f44455;
border-color: #f44455;
}
.pass-color {
color: #fff;
background-color: #5fc27e;
border-color: #5fc27e;
}
.fail-color {
color: #fff;
background-color: #fcc100;
border-color: #fcc100;
}
.skip-color {
color: #fff;
background-color: #6c757d;
border-color: #6c757d;
}
/* -- screenshots ---------------------------------------------------------------------- */
.img{
height: 100%;
border-collapse: collapse;
}
.screenshots {
z-index: 100;
position:fixed;
height: 80%;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
display: none;
box-shadow:1px 2px 20px #333333;
}
.imgyuan{
height: 20px;
border-radius: 12px;
background-color: red;
padding-left: 13px;
margin: 0 auto;
position: relative;
top: -40px;
background-color: rgba(1, 150, 0, 0.3);
}
.imgyuan font{
border:1px solid white;
width:11px;
height:11px;
border-radius:50%;
margin-right: 9px;
margin-top: 4px;
display: block;
float: left;
background-color: white;
}
.close_shots {
background-image: url(data:image/png;base64,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);
background-size: 22px 22px;
-moz-background-size: 22px 22px;
background-repeat: no-repeat;
position: absolute;
top: 5px;
right: 5px;
height: 22px;
z-index: 99;
width: 22px;
ox-shadow:1px 2px 5px #333333;
}
</style>
"""
# ------------------------------------------------------------------------
# Heading
#
HEADING_TMPL = """
<nav class="navbar navbar-expand navbar-light bg-white">
<a class="sidebar-toggle d-flex mr-2">
<i class="hamburger align-self-center"></i>
</a>
<h1 style="margin-bottom: 0px;">seldom</h1>
<div class="navbar-collapse collapse">
<ul class="navbar-nav ml-auto">
<h3 style="float: right;">%(title)s</h3>
</ul>
</div>
</nav>
<div style="height: 260px; margin-top: 20px;">
<div class="col-12 col-lg-5 col-xl-3 d-flex" style="float:left">
<div class='card flex-fill'>
<div class="card-body my-2">
<table class="table my-0">
<tbody>
%(parameters)s
<tr><td>Description:</td><td class="text-right">%(description)s</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div style="float:left; margin-left: 10px; margin-top: 20px;">
<p> Test Case Pie charts </p>
<h2 class="d-flex align-items-center mb-0 font-weight-light pass-color">%(pass_count)s</h2>
<a>PASSED</a><br>
<h2 class="d-flex align-items-center mb-0 font-weight-light fail-color">%(fail_count)s</h2>
<a>FAILED</a>
<h2 class="d-flex align-items-center mb-0 font-weight-light error-color">%(error_count)s</h2>
<a>ERRORS</a><br>
<h2 class="d-flex align-items-center mb-0 font-weight-light skip-color">%(skip_count)s</h2>
<a>SKIPED</a><br>
</div>
<div class="testChars">
<canvas id="myChart" width="250" height="250"></canvas>
</div>
</div>
""" # variables: (title, parameters, description)
# ------------------------------------------------------------------------
# Pie chart
#
ECHARTS_SCRIPT = """
<script type="text/javascript">
var data = [
{
value: %(error)s,
color: "#f44455",
label: "Error",
labelColor: 'white',
labelFontSize: '16'
},
{
value : %(fail)s,
color : "#fcc100",
label: "Fail",
labelColor: 'white',
labelFontSize: '16'
},
{
value : %(Pass)s,
color : "#5fc27e",
label : "Pass",
labelColor: 'white',
labelFontSize: '16'
},
{
value : %(skip)s,
color : "#6c757d",
label : "skip",
labelColor: 'white',
labelFontSize: '16'
}
]
var newopts = {
animationSteps: 100,
animationEasing: 'easeInOutQuart',
}
//Get the context of the canvas element we want to select
var ctx = document.getElementById("myChart").getContext("2d");
var myNewChart = new Chart(ctx).Pie(data,newopts);
</script>
"""
HEADING_ATTRIBUTE_TMPL = """<tr><td>%(name)s:</td><td class="text-right">%(value)s</td></tr>
""" # variables: (name, value)
# ------------------------------------------------------------------------
# Report
#
REPORT_TMPL = """
<p id='show_detail_line' style="margin-left: 10px; margin-top: 30px;">
<a href='javascript:showCase(0, %(channel)s)' class="btn btn-dark btn-sm">Summary</a>
<a href='javascript:showCase(1, %(channel)s)' class="btn btn-success btn-sm">Pass</a>
<a href='javascript:showCase(2, %(channel)s)' class="btn btn-warning btn-sm">Failed</a>
<a href='javascript:showCase(3, %(channel)s)' class="btn btn-danger btn-sm">Error</a>
<a href='javascript:showCase(4, %(channel)s)' class="btn btn-light btn-sm">Skip</a>
<a href='javascript:showCase(5, %(channel)s)' class="btn btn-info btn-sm">All</a>
</p>
<table class="table mb-0">
<thead>
<tr id='header_row'>
<td>Test Group/Test case</td>
<td>Count</td>
<td>Pass</td>
<td>Fail</td>
<td>Error</td>
<td>View</td>
<td>Screenshots</td>
</tr>
</thead>
%(test_list)s
<tr id='total_row'>
<td>Total</td>
<td>%(count)s</td>
<td class="text text-success">%(Pass)s</td>
<td class="text text-danger">%(fail)s</td>
<td class="text text-warning">%(error)s</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
</table>
""" # variables: (test_list, count, Pass, fail, error)
REPORT_CLASS_TMPL = r"""
<tr class='%(style)s'>
<td>%(desc)s</td>
<td>%(count)s</td>
<td>%(Pass)s</td>
<td>%(fail)s</td>
<td>%(error)s</td>
<td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">Detail</a></td>
<td>&nbsp;</td>
</tr>
""" # variables: (style, desc, count, Pass, fail, error, cid)
REPORT_TEST_WITH_OUTPUT_TMPL = r"""
<tr id='%(tid)s' class='%(Class)s'>
<td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
<td colspan='5' align='center'>
<!--css div popup start-->
<a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" >
%(status)s</a>
<div id='div_%(tid)s' class="popup_window">
<div style='text-align: right; color:red;cursor:pointer'>
<a onfocus='this.blur();' onclick="document.getElementById('div_%(tid)s').style.display = 'none' " >
[x]</a>
</div>
<pre>
%(script)s
</pre>
</div>
<!--css div popup end-->
</td>
<td>%(img)s</td>
</tr>
""" # variables: (tid, Class, style, desc, status)
REPORT_TEST_NO_OUTPUT_TMPL = r"""
<tr id='%(tid)s' class='%(Class)s'>
<td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
<td colspan='5' align='center'>%(status)s</td>
<td>%(img)s</td>
</tr>
""" # variables: (tid, Class, style, desc, status)
REPORT_TEST_OUTPUT_TMPL = r"""
%(id)s: %(output)s
""" # variables: (id, output)
IMG_TMPL = r"""
<a onfocus='this.blur();' href="javacript:void(0);" onclick="show_img(this)">show</a>
<div align="center" class="screenshots" style="display:none">
<a class="close_shots" onclick="hide_img(this)"></a>
{imgs}
<div class="imgyuan"></div>
</div>
"""
# ------------------------------------------------------------------------
# ENDING
#
ENDING_TMPL = """<div id='ending'>&nbsp;</div>"""
# -------------------- The end of the Template class -------------------
TestResult = unittest.TestResult
class _TestResult(TestResult):
# note: _TestResult is a pure representation of results.
# It lacks the output and reporting ability compares to unittest._TextTestResult.
def __init__(self, verbosity=1, rerun=0, save_last_run=False):
TestResult.__init__(self)
self.stdout0 = None
self.stderr0 = None
self.success_count = 0
self.failure_count = 0
self.error_count = 0
self.skip_count = 0
self.verbosity = verbosity
# result is a list of result in 4 tuple
# (
# result code (0: success; 1: fail; 2: error; 3: skip),
# TestCase object,
# Test output (byte string),
# stack trace,
# )
self.rerun = rerun
self.save_last_run = save_last_run
self.status = 0
self.runs = 0
self.result = []
def startTest(self, test):
test.imgs = getattr(test, "imgs", [])
# TestResult.startTest(self, test)
# just one buffer for both stdout and stderr
self.outputBuffer = io.StringIO()
stdout_redirector.fp = self.outputBuffer
stderr_redirector.fp = self.outputBuffer
self.stdout0 = sys.stdout
self.stderr0 = sys.stderr
sys.stdout = stdout_redirector
sys.stderr = stderr_redirector
def complete_output(self):
"""
Disconnect output redirection and return buffer.
Safe to call multiple times.
"""
if self.stdout0:
sys.stdout = self.stdout0
sys.stderr = self.stderr0
self.stdout0 = None
self.stderr0 = None
return self.outputBuffer.getvalue()
def stopTest(self, test):
# Usually one of addSuccess, addError or addFailure would have been called.
# But there are some path in unittest that would bypass this.
# We must disconnect stdout in stopTest(), which is guaranteed to be called.
if self.rerun and self.rerun >= 1:
if self.status == 1:
self.runs += 1
if self.runs <= self.rerun:
if self.save_last_run:
t = self.result.pop(-1)
if t[0] == 1:
self.failure_count -= 1
else:
self.error_count -= 1
test = copy.copy(test)
sys.stderr.write("Retesting... ")
sys.stderr.write(str(test))
sys.stderr.write('..%d \n' % self.runs)
doc = getattr(test, '_testMethodDoc', u"") or u''
if doc.find('->rerun') != -1:
doc = doc[:doc.find('->rerun')]
desc = "%s->rerun:%d" % (doc, self.runs)
if isinstance(desc, str):
desc = desc
test._testMethodDoc = desc
test(self)
else:
self.status = 0
self.runs = 0
self.complete_output()
def addSuccess(self, test):
self.success_count += 1
self.status = 0
TestResult.addSuccess(self, test)
output = self.complete_output()
self.result.append((0, test, output, ''))
if self.verbosity > 1:
sys.stderr.write('ok ')
sys.stderr.write(str(test))
sys.stderr.write('\n')
else:
sys.stderr.write('.' + str(self.success_count))
def addError(self, test, err):
self.error_count += 1
self.status = 1
TestResult.addError(self, test, err)
_, _exc_str = self.errors[-1]
output = self.complete_output()
self.result.append((2, test, output, _exc_str))
if not getattr(test, "driver", ""):
pass
else:
try:
driver = getattr(test, "driver")
test.imgs.append(driver.get_screenshot_as_base64())
except BaseException:
pass
if self.verbosity > 1:
sys.stderr.write('E ')
sys.stderr.write(str(test))
sys.stderr.write('\n')
else:
sys.stderr.write('E')
def addFailure(self, test, err):
self.failure_count += 1
self.status = 1
TestResult.addFailure(self, test, err)
_, _exc_str = self.failures[-1]
output = self.complete_output()
self.result.append((1, test, output, _exc_str))
if not getattr(test, "driver", ""):
pass
else:
try:
driver = getattr(test, "driver")
test.imgs.append(driver.get_screenshot_as_base64())
except BaseException:
pass
if self.verbosity > 1:
sys.stderr.write('F ')
sys.stderr.write(str(test))
sys.stderr.write('\n')
else:
sys.stderr.write('F')
def addSkip(self, test, reason):
self.skip_count += 1
self.status = 0
TestResult.addSkip(self, test, reason)
output = self.complete_output()
self.result.append((3, test, output, reason))
if self.verbosity > 1:
sys.stderr.write('S')
sys.stderr.write(str(test))
sys.stderr.write('\n')
else:
sys.stderr.write('S')
class HTMLTestRunner(Template_mixin):
"""
"""
def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None, save_last_run=True):
self.stream = stream
self.verbosity = verbosity
self.save_last_run = save_last_run
self.run_times = 0
if title is None:
self.title = self.DEFAULT_TITLE
else:
self.title = title
if description is None:
self.description = self.DEFAULT_DESCRIPTION
else:
self.description = description
self.startTime = datetime.datetime.now()
def run(self, test, rerun=0, save_last_run=False):
"""Run the given test case or test suite."""
result = _TestResult(self.verbosity, rerun=rerun, save_last_run=save_last_run)
test(result)
self.stopTime = datetime.datetime.now()
self.run_times += 1
self.generateReport(test, result)
# print(sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime))
return result
def sortResult(self, result_list):
# unittest does not seems to run in any particular order.
# Here at least we want to group them together by class.
rmap = {}
classes = []
for n, t, o, e in result_list:
cls = t.__class__
if not cls in rmap:
rmap[cls] = []
classes.append(cls)
rmap[cls].append((n, t, o, e))
r = [(cls, rmap[cls]) for cls in classes]
return r
def getReportAttributes(self, result):
"""
Return report attributes as a list of (name, value).
Override this to add custom attributes.
"""
startTime = str(self.startTime)[:19]
duration = str(self.stopTime - self.startTime)
status = []
if result.success_count:
status.append('Passed:%s' % result.success_count)
if result.failure_count:
status.append('Failed:%s' % result.failure_count)
if result.error_count:
status.append('Errors:%s' % result.error_count)
if result.skip_count:
status.append('Skiped:%s' % result.skip_count)
if status:
status = ' '.join(status)
else:
status = 'none'
result = {
"pass": result.success_count,
"fail": result.failure_count,
"error": result.error_count,
"skip": result.skip_count,
}
return [
('Start Time', startTime),
('Duration', duration),
('Status', status),
('Result', result),
]
def generateReport(self, test, result):
report_attrs = self.getReportAttributes(result)
generator = 'HTMLTestRunner %s' % __version__
stylesheet = self._generate_stylesheet()
heading = self._generate_heading(report_attrs)
report = self._generate_report(result)
ending = self._generate_ending()
chart = self._generate_chart(result)
output = self.HTML_TMPL % dict(
title=saxutils.escape(self.title),
generator=generator,
stylesheet=stylesheet,
heading=heading,
report=report,
ending=ending,
chart_script=chart,
channel=self.run_times,
)
self.stream.write(output.encode('utf8'))
def _generate_stylesheet(self):
return self.STYLESHEET_TMPL
def _generate_heading(self, report_attrs):
a_lines = []
for name, value in report_attrs:
result = {}
if name == "Result":
result = value
else:
line = self.HEADING_ATTRIBUTE_TMPL % dict(
name=saxutils.escape(name),
value=saxutils.escape(value),
)
a_lines.append(line)
heading = self.HEADING_TMPL % dict(
title=saxutils.escape(self.title),
parameters=''.join(a_lines),
description=saxutils.escape(self.description),
pass_count=saxutils.escape(str(result["pass"])),
fail_count=saxutils.escape(str(result["fail"])),
error_count=saxutils.escape(str(result["error"])),
skip_count=saxutils.escape(str(result["skip"])),
)
return heading
def _generate_report(self, result):
rows = []
sortedResult = self.sortResult(result.result)
for cid, (cls, cls_results) in enumerate(sortedResult):
# subtotal for a class
np = nf = ne = ns = 0
for n, t, o, e in cls_results:
if n == 0:
np += 1
elif n == 1:
nf += 1
elif n == 2:
ne += 1
else:
ns += 1
# format class description
if cls.__module__ == "__main__":
name = cls.__name__
else:
name = "%s.%s" % (cls.__module__, cls.__name__)
doc = cls.__doc__ or ""
desc = doc and '%s: %s' % (name, doc) or name
row = self.REPORT_CLASS_TMPL % dict(
style=ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
desc=desc,
count=np + nf + ne,
Pass=np,
fail=nf,
error=ne,
cid='c%s.%s' % (self.run_times, cid + 1),
)
rows.append(row)
for tid, (n, t, o, e) in enumerate(cls_results):
self._generate_report_test(rows, cid, tid, n, t, o, e)
report = self.REPORT_TMPL % dict(
test_list=''.join(rows),
count=str(result.success_count + result.failure_count + result.error_count),
Pass=str(result.success_count),
fail=str(result.failure_count),
error=str(result.error_count),
skip=str(result.skip_count),
total=str(result.success_count + result.failure_count + result.error_count),
channel=str(self.run_times),
)
return report
def _generate_chart(self, result):
chart = self.ECHARTS_SCRIPT % dict(
Pass=str(result.success_count),
fail=str(result.failure_count),
error=str(result.error_count),
skip=str(result.skip_count),
)
return chart
def _generate_report_test(self, rows, cid, tid, n, t, o, e):
# e.g. 'pt1.1', 'ft1.1','et1.1', 'st1.1' etc
has_output = bool(o or e)
if n == 0:
tmp = "p"
elif n == 1:
tmp = "f"
elif n == 2:
tmp = "e"
else:
tmp = "s"
tid = tmp + 't%d.%d.%d' % (self.run_times, cid + 1, tid + 1)
# tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid + 1, tid + 1)
name = t.id().split('.')[-1]
doc = t.shortDescription() or ""
desc = doc and ('%s: %s' % (name, doc)) or name
tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL
# o and e should be byte string because they are collected from stdout and stderr?
if isinstance(o, str):
# TODO: some problem with 'string_escape': it escape \n and mess up formating
# uo = unicode(o.encode('string_escape'))
uo = o
else:
uo = o
if isinstance(e, str):
# TODO: some problem with 'string_escape': it escape \n and mess up formating
# ue = unicode(e.encode('string_escape'))
ue = e
else:
ue = e
script = self.REPORT_TEST_OUTPUT_TMPL % dict(
id=tid,
output=saxutils.escape(uo + ue),
)
if getattr(t, 'imgs', []):
# 判断截图列表,如果有则追加
tmp = ""
for i, img in enumerate(t.imgs):
if i == 0:
tmp += """<img src="data:image/jpg;base64,{}" style="display: block;" class="img"/>\n""".format(img)
else:
tmp += """<img src="data:image/jpg;base64,{}" style="display: none;" class="img"/>\n""".format(img)
screenshots_html = self.IMG_TMPL.format(imgs=tmp)
else:
screenshots_html = """"""
row = tmpl % dict(
tid=tid,
Class=(n == 0 and 'hiddenRow' or 'none'),
style=n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'passCase'),
desc=desc,
script=script,
status=self.STATUS[n],
img=screenshots_html
)
rows.append(row)
if not has_output:
return
def _generate_ending(self):
return self.ENDING_TMPL
##############################################################################
# Facilities for running tests from the command line
##############################################################################
# Note: Reuse unittest.TestProgram to launch test. In the future we may
# build our own launcher to support more specific command line
# parameters like test title, CSS, etc.
class TestProgram(unittest.TestProgram):
"""
A variation of the unittest.TestProgram. Please refer to the base
class for command line parameters.
"""
def runTests(self):
# Pick HTMLTestRunner as the default test runner.
# base class's testRunner parameter is not useful because it means
# we have to instantiate HTMLTestRunner before we know self.verbosity.
if self.testRunner is None:
self.testRunner = HTMLTestRunner(verbosity=self.verbosity)
unittest.TestProgram.runTests(self)
main = TestProgram
##############################################################################
# Executing this module from the command line
##############################################################################
if __name__ == "__main__":
main(module=None)
\ No newline at end of file
import time
import os
def year_to_minute():
return time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))
def year_to_day():
return time.strftime('%Y%m%d', time.localtime(time.time()))
def file_abspath():
return os.path.abspath(__file__).split('src')[0]
\ No newline at end of file
import logging
import os
from src.framework import common
class Logger(object):
def __init__(self, logger):
"""指定保存日志的文件路径,日志级别,以及调用文件,将日志存入到指定的文件中"""
# 创建一个logger
self.logger = logging.getLogger(logger)
self.logger.setLevel(logging.DEBUG)
# 创建一个handler,用于写入日志文件
rq = common.year_to_minute()
today = common.year_to_day()
log_dir = common.file_abspath() + '/logs/' + today + '/'
# log_dir = os.path.abspath(__file__).split('src')[0] + '/logs/' + today + '/'
if not os.path.exists(log_dir):
os.mkdir(log_dir)
log_name = log_dir + rq + '.log'
fh = logging.FileHandler(log_name)
fh.setLevel(logging.INFO)
# 再创建一个handler,用于输出到控制台
ch = logging.StreamHandler()
# ch.setLevel(logging.INFO)
ch.setLevel(logging.ERROR)
# 定义handler的输出格式
formatter = logging.Formatter('%(levelname)s :%(name)s:%(asctime)s:%(message)s"')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# 给logger添加handler
self.logger.addHandler(fh)
self.logger.addHandler(ch)
def getlog(self):
return self.logger
\ No newline at end of file
from time import sleep
# pages基类
from selenium.common.exceptions import NoSuchElementException
class Page(object):
"""
Page基类,所有page都应该继承该类
"""
def __init__(self, driver):
self.driver = driver
self.timeout = 30
# 寻找单元素或唯一元素
def find_element(self, *loc):
return self.driver.find_element(*loc)
# 输入元素输入
def input_text(self, loc, text):
self.find_element(*loc).send_keys(text)
# 点击框点击
def click(self, loc):
self.find_element(*loc).click()
def get_title(self):
return self.driver.title
# 打开url站点
def open_url(self, url):
self.driver.get(url)
# 获取当前url
def current_url(self):
self.driver.current_url()
# 关闭浏览器
def quit_browser(self):
self.driver.quit()
# 浏览器前进操作
def forward(self):
self.driver.forward()
# 浏览器后退操作
def back(self):
self.driver.back()
# 清空输入框
def clear(self):
self.driver.clear()
# 隐式等待
def wait(self, seconds):
self.driver.implicitly_wait(seconds)
# 强制等待
def sleep(self, seconds):
return sleep(seconds)
# 屏幕最大化
def max(self):
self.driver.maximize_window()
# 判断元素是否存在于页面
def ifElementExist(self, loc):
try:
self.driver.find_element(*loc)
except NoSuchElementException:
# 打印异常信息
print("NoSuchElementException")
# 发生异常,说明页面中未找到该元素,返回False
return False
else:
print("HaveSuchElement{}".format(loc))
# 无异常,说明在页面中找到了该元素,返回True
return True
# 寻找多元素返回
def find_elements(self, *loc):
return self.driver.find_elements(*loc)
# 关闭窗口
def close(self, loc, num):
self.find_elements(*loc)[num].click()
\ No newline at end of file
from src.pageobject.basepage import Page
from selenium.webdriver.common.by import By
# 登录页面
class LoginPage(Page):
# 元素集
# 手机号登录按钮
phone_login = (By.ID, "rc-tabs-0-tab-2")
# 账号输入框
account_input = (By.ID, "xmphone")
# 密码输入框
password_input = (By.ID, "xmpwd")
# 登录按钮
login_button = (By.CLASS_NAME, "submit")
# 验证元素
# 店铺名称
store_name = (By.CLASS_NAME, "inst")
def __init__(self, driver):
Page.__init__(self, driver)
# 点击手机号登录,展示输入框
def click_phone_login(self):
self.click(self.phone_login)
# 输入账号
def input_account(self, account):
self.input_text(self.account_input, account)
# 输入密码
def input_password(self, password):
self.input_text(self.password_input, password)
# 点击登录
def click_login(self):
self.click(self.login_button)
from src.pageobject.loginpage import LoginPage
from selenium.webdriver.common.by import By
# 主页面
class HomePage(LoginPage):
# 元素集
# '未完成任务'按钮
incomplete = (By.XPATH, "//div[text()='未完成任务']")
# '未完成任务'按钮
completed = (By.XPATH, "//div[text()='已完成任务']")
# 荣誉榜按钮
honors = (By.XPATH, "//span[text()='荣誉榜']")
# 修改密码按钮
modifypassword = (By.XPATH, "//span[text()='修改密码']")
# 修改信息按钮
modifyinformation = (By.XPATH, "//span[text()='修改信息']")
# 退出系统按钮
logout = (By.XPATH, "//span[text()='退出系统']")
# 单词量测试按钮
wordtest = (By.XPATH, "//div[text()='单词量测试']")
# 验证元素
# 荣誉榜-总排行
total_tanking = (By.XPATH, "//li[text()='总排行']")
# 修改密码-确认密码
confirm_password = (By.CLASS_NAME, "title")
# 修改信息-姓名
name = (By.CLASS_NAME, "title.short")
# 单词量测试-学段测试
stage_test = (By.XPATH, "//div[text()='学段测试']")
def __init__(self, driver):
LoginPage.__init__(self, driver)
# 点击'未完成任务'
def click_incomplete(self):
self.click(self.incomplete)
# 点击'已完成任务'
def click_completed(self):
self.click(self.completed)
# 点击'荣誉榜'
def click_honors(self):
self.click(self.honors)
# 点击'修改密码'
def click_modifypassword(self):
self.click(self.modifypassword)
# 点击'修改信息'
def click_modifyinformation(self):
self.click(self.modifyinformation)
# 点击'退出系统'
def click_logout(self):
self.click(self.logout)
# 点击'单词量测试'
def click_wordtest(self):
self.click(self.wordtest)
\ No newline at end of file
from src.pageobject.loginpage import LoginPage
from src.framework.logger import Logger
from config import readConfig
from commom.appDriver import get_app_driver
import unittest
class TestLoginPage(unittest.TestCase):
logger = Logger('login page').getlog()
account = readConfig.test_account()
password = readConfig.test_password()
cloud_class_location = readConfig.test_location()
def setUp(self) -> None:
print(11111)
# 指定chrome的webdriver路径
self.driver = get_app_driver(self.cloud_class_location)
def tearDown(self) -> None:
self.driver.quit()
# 测试登录
def testLogin(self):
driver = self.driver
login_page = LoginPage(driver=driver)
login_page.click_phone_login()
login_page.input_account(account=self.account)
login_page.input_password(password=self.password)
login_page.click_login()
login_page.sleep(1)
self.assertTrue(login_page.ifElementExist(login_page.store_name))
if __name__ == '__main__':
unittest.main(verbosity=1)
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