Commit ab29ef03 authored by vabr's avatar vabr Committed by Commit bot

[Password manager Python tests] Remove some dead code

This CL removes:
* Unused support for logging in environment.py. The two current diagnostics messages will be replaced later if needed, after the code gets rearranged. The command-line support for log levels was not used (and not working last time I tried).
* Some unused code was removed, including WebsiteTest's SendEnterTo action.
* It simplified the way to obtain all tests names -- no longer needed to instantiate Environment.
* It removes adding ../../../../third_party/webdriver/pylib/ to sys.path -- this has not worked in most cases and was not needed (webdriver path has been added, from various sources, outside of the scripts in the framework we used).

It also removes some part of the unused ability to run all tests, which has recently been simulated by running all tests one by one outside of Python. That was to make the tests run in parallel. This is just a transitional state, and is described in the TODOs.

BUG=369521

Review URL: https://codereview.chromium.org/1022703004

Cr-Commit-Position: refs/heads/master@{#322447}
parent 70deb4f4
...@@ -109,8 +109,6 @@ self.ClickIfClickable("css_selector") ...@@ -109,8 +109,6 @@ self.ClickIfClickable("css_selector")
self.GoTo("url") self.GoTo("url")
* HoverOver: find an element using CSS Selector and hover over it. * HoverOver: find an element using CSS Selector and hover over it.
self.HoverOver("css_selector") self.HoverOver("css_selector")
* SendEnterTo: find an element using CSS Selector and send enter to it.
self.SendEnterTo("css_selector")
* IsDisplayed: check if an element is displayed. * IsDisplayed: check if an element is displayed.
self.IsDisplayed("css_selector") self.IsDisplayed("css_selector")
......
...@@ -4,19 +4,12 @@ ...@@ -4,19 +4,12 @@
"""The testing Environment class.""" """The testing Environment class."""
import logging import os
import shutil import shutil
import sys
import time import time
import traceback
from xml.etree import ElementTree from xml.etree import ElementTree
from xml.sax.saxutils import escape
sys.path.insert(0, '../../../../third_party/webdriver/pylib/')
from selenium import webdriver from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.options import Options
...@@ -46,8 +39,7 @@ class Environment: ...@@ -46,8 +39,7 @@ class Environment:
"""Sets up the testing Environment. """ """Sets up the testing Environment. """
def __init__(self, chrome_path, chromedriver_path, profile_path, def __init__(self, chrome_path, chromedriver_path, profile_path,
passwords_path, enable_automatic_password_saving, passwords_path, enable_automatic_password_saving):
numeric_level=None, log_to_console=False, log_file=""):
"""Creates a new testing Environment. """Creates a new testing Environment.
Args: Args:
...@@ -57,61 +49,35 @@ class Environment: ...@@ -57,61 +49,35 @@ class Environment:
passwords_path: The usernames and passwords file. passwords_path: The usernames and passwords file.
enable_automatic_password_saving: If True, the passwords are going to be enable_automatic_password_saving: If True, the passwords are going to be
saved without showing the prompt. saved without showing the prompt.
numeric_level: The log verbosity.
log_to_console: If True, the debug logs will be shown on the console.
log_file: The file where to store the log. If it's empty, the log will
not be stored.
Raises: Raises:
Exception: An exception is raised if |profile_path| folder could not be Exception: An exception is raised if |profile_path| folder could not be
removed. removed.
""" """
# Setting up the login.
if numeric_level is not None:
if log_file:
# Set up logging to file.
logging.basicConfig(level=numeric_level,
filename=log_file,
filemode='w')
if log_to_console:
console = logging.StreamHandler()
console.setLevel(numeric_level)
# Add the handler to the root logger.
logging.getLogger('').addHandler(console)
elif log_to_console:
logging.basicConfig(level=numeric_level)
# Cleaning the chrome testing profile folder. # Cleaning the chrome testing profile folder.
try: if os.path.exists(profile_path):
shutil.rmtree(profile_path) shutil.rmtree(profile_path)
except Exception, e: options = Options()
pass self.enable_automatic_password_saving = enable_automatic_password_saving
# If |chrome_path| is not defined, this means that we are in the dashboard if enable_automatic_password_saving:
# website, and we just need to get the list of all websites. In this case, options.add_argument("enable-automatic-password-saving")
# we don't need to initilize the webdriver. # Chrome path.
if chrome_path: options.binary_location = chrome_path
options = Options() # Chrome testing profile path.
self.enable_automatic_password_saving = enable_automatic_password_saving options.add_argument("user-data-dir=%s" % profile_path)
if enable_automatic_password_saving:
options.add_argument("enable-automatic-password-saving") # The webdriver. It's possible to choose the port the service is going to
# Chrome path. # run on. If it's left to 0, a free port will be found.
options.binary_location = chrome_path self.driver = webdriver.Chrome(chromedriver_path, 0, options)
# Chrome testing profile path. # The password internals window.
options.add_argument("user-data-dir=%s" % profile_path) self.internals_window = self.driver.current_window_handle
if passwords_path:
# The webdriver. It's possible to choose the port the service is going to # An xml tree filled with logins and passwords.
# run on. If it's left to 0, a free port will be found. self.passwords_tree = ElementTree.parse(passwords_path).getroot()
self.driver = webdriver.Chrome(chromedriver_path, 0, options) else:
# The password internals window. raise Exception("Error: |passwords_path| needs to be provided if"
self.internals_window = self.driver.current_window_handle "|chrome_path| is provided, otherwise the tests could not be run")
if passwords_path:
# An xml tree filled with logins and passwords.
self.passwords_tree = ElementTree.parse(passwords_path).getroot()
else:
raise Exception("Error: |passwords_path| needs to be provided if"
"|chrome_path| is provided, otherwise the tests could not be run")
# Password internals page. # Password internals page.
self.internals_page = "chrome://password-manager-internals/" self.internals_page = "chrome://password-manager-internals/"
# The Website window. # The Website window.
...@@ -119,9 +85,7 @@ class Environment: ...@@ -119,9 +85,7 @@ class Environment:
# The WebsiteTests list. # The WebsiteTests list.
self.websitetests = [] self.websitetests = []
# Map messages to the number of their appearance in the log. # Map messages to the number of their appearance in the log.
self.message_count = dict() self.message_count = { MESSAGE_ASK: 0, MESSAGE_SAVE: 0 }
self.message_count[MESSAGE_ASK] = 0
self.message_count[MESSAGE_SAVE] = 0
# The tests needs two tabs to work. A new tab is opened with the first # The tests needs two tabs to work. A new tab is opened with the first
# GoTo. This is why we store here whether or not it's the first time to # GoTo. This is why we store here whether or not it's the first time to
# execute GoTo. # execute GoTo.
...@@ -132,25 +96,29 @@ class Environment: ...@@ -132,25 +96,29 @@ class Environment:
def AddWebsiteTest(self, websitetest): def AddWebsiteTest(self, websitetest):
"""Adds a WebsiteTest to the testing Environment. """Adds a WebsiteTest to the testing Environment.
TODO(vabr): Currently, this is only called at most once for each
Environment instance. That is because to run all tests efficiently in
parallel, each test gets its own process spawned (outside of Python).
That makes sense, but then we should flatten the hierarchy of calls
and consider making the 1:1 relation of environment to tests more
explicit.
Args: Args:
websitetest: The WebsiteTest instance to be added. websitetest: The WebsiteTest instance to be added.
""" """
websitetest.environment = self websitetest.environment = self
if hasattr(self, "driver"): # TODO(vabr): Make driver a property of WebsiteTest.
websitetest.driver = self.driver websitetest.driver = self.driver
if hasattr(self, "passwords_tree") and self.passwords_tree is not None: if not websitetest.username:
if not websitetest.username: username_tag = (
username_tag = ( self.passwords_tree.find(
self.passwords_tree.find( ".//*[@name='%s']/username" % websitetest.name))
".//*[@name='%s']/username" % websitetest.name)) websitetest.username = username_tag.text
if username_tag.text: if not websitetest.password:
websitetest.username = username_tag.text password_tag = (
if not websitetest.password: self.passwords_tree.find(
password_tag = ( ".//*[@name='%s']/password" % websitetest.name))
self.passwords_tree.find( websitetest.password = password_tag.text
".//*[@name='%s']/password" % websitetest.name))
if password_tag.text:
websitetest.password = password_tag.text
self.websitetests.append(websitetest) self.websitetests.append(websitetest)
def ClearCache(self, clear_passwords): def ClearCache(self, clear_passwords):
...@@ -160,7 +128,6 @@ class Environment: ...@@ -160,7 +128,6 @@ class Environment:
Args: Args:
clear_passwords : Clear all the passwords if the bool value is true. clear_passwords : Clear all the passwords if the bool value is true.
""" """
logging.info("\nClearCache\n")
self.driver.get("chrome://settings/clearBrowserData") self.driver.get("chrome://settings/clearBrowserData")
self.driver.switch_to_frame("settings") self.driver.switch_to_frame("settings")
script = ( script = (
...@@ -184,7 +151,6 @@ class Environment: ...@@ -184,7 +151,6 @@ class Environment:
self.EnablePasswordsSaving() self.EnablePasswordsSaving()
def EnablePasswordsSaving(self): def EnablePasswordsSaving(self):
logging.info("\nEnablePasswordSaving\n")
self.driver.get("chrome://settings") self.driver.get("chrome://settings")
self.driver.switch_to_frame("settings") self.driver.switch_to_frame("settings")
script = "document.getElementById('advanced-settings-expander').click();" script = "document.getElementById('advanced-settings-expander').click();"
...@@ -294,6 +260,8 @@ class Environment: ...@@ -294,6 +260,8 @@ class Environment:
def AllTests(self, prompt_test): def AllTests(self, prompt_test):
"""Runs the tests on all the WebsiteTests. """Runs the tests on all the WebsiteTests.
TODO(vabr): Currently, "all tests" always means one.
Args: Args:
prompt_test: If True, tests caring about showing the save-password prompt_test: If True, tests caring about showing the save-password
prompt are going to be run, otherwise tests which don't care about prompt are going to be run, otherwise tests which don't care about
......
...@@ -36,7 +36,6 @@ import subprocess ...@@ -36,7 +36,6 @@ import subprocess
import tempfile import tempfile
import time import time
from environment import Environment
import tests import tests
...@@ -173,7 +172,6 @@ def run_tests(config_path): ...@@ -173,7 +172,6 @@ def run_tests(config_path):
for format description. for format description.
""" """
environment = Environment("", "", "", None, False)
defaults = {("run_options", "tests_in_parallel"): "1"} defaults = {("run_options", "tests_in_parallel"): "1"}
config = ConfigParser.ConfigParser() config = ConfigParser.ConfigParser()
_apply_defaults(config, defaults) _apply_defaults(config, defaults)
...@@ -194,8 +192,7 @@ def run_tests(config_path): ...@@ -194,8 +192,7 @@ def run_tests(config_path):
user_selected_tests = config.get("run_options", "tests_to_run").split(",") user_selected_tests = config.get("run_options", "tests_to_run").split(",")
tests_to_run = user_selected_tests tests_to_run = user_selected_tests
else: else:
tests.Tests(environment) tests_to_run = tests.all_tests.keys()
tests_to_run = [test.name for test in environment.websitetests]
logger = logging.getLogger("run_tests") logger = logging.getLogger("run_tests")
logger.log(SCRIPT_DEBUG, "%d tests to run: %s", len(tests_to_run), logger.log(SCRIPT_DEBUG, "%d tests to run: %s", len(tests_to_run),
......
...@@ -6,23 +6,11 @@ ...@@ -6,23 +6,11 @@
"""Automated tests for many websites""" """Automated tests for many websites"""
import argparse import argparse
import logging
from environment import Environment from environment import Environment
from websitetest import WebsiteTest from websitetest import WebsiteTest
class TypeOfTestedWebsites:
"""An enum to specify which groups of tests to run."""
# Runs all the tests.
ALL_TESTS = 0
# Runs a specified list of tests.
LIST_OF_TESTS = 1
def __init__(self):
pass
class Alexa(WebsiteTest): class Alexa(WebsiteTest):
def Login(self): def Login(self):
...@@ -445,53 +433,46 @@ class Ziddu(WebsiteTest): ...@@ -445,53 +433,46 @@ class Ziddu(WebsiteTest):
self.Click(".login input") self.Click(".login input")
def Tests(environment, tests_to_run=None): all_tests = {
"163": One63("163"), # http://crbug.com/368690
all_tests = { "adobe": Adobe("adobe"), # Password saving not offered.
"163": One63("163"), # http://crbug.com/368690 "alexa": Alexa("alexa"),
"adobe": Adobe("adobe"), # Password saving not offered. "aliexpress": Aliexpress("aliexpress"), # Fails due to test framework issue.
"alexa": Alexa("alexa"), "amazon": Amazon("amazon"), # Bug not reproducible without test.
"aliexpress": Aliexpress("aliexpress"), # Fails due to test framework issue. "ask": Ask("ask"), # Password not saved.
"amazon": Amazon("amazon"), # Bug not reproducible without test. "baidu": Baidu("baidu"), # Password not saved.
"ask": Ask("ask"), # Password not saved. "cnn": Cnn("cnn"), # http://crbug.com/368690
"baidu": Baidu("baidu"), # Password not saved. "craigslist": Craigslist("craigslist"), # Too many failed logins per time.
"cnn": Cnn("cnn"), # http://crbug.com/368690 "dailymotion": Dailymotion("dailymotion"), # Crashes.
"craigslist": Craigslist("craigslist"), # Too many failed logins per time. "dropbox": Dropbox("dropbox"),
"dailymotion": Dailymotion("dailymotion"), # Crashes. "ebay": Ebay("ebay"), # http://crbug.com/368690
"dropbox": Dropbox("dropbox"), "espn": Espn("espn"), # Iframe, password saved but not autofilled.
"ebay": Ebay("ebay"), # http://crbug.com/368690 "facebook": Facebook("facebook"),
"espn": Espn("espn"), # Iframe, password saved but not autofilled. "flipkart": Flipkart("flipkart"), # Fails due to test framework issue.
"facebook": Facebook("facebook"), "github": Github("github"),
"flipkart": Flipkart("flipkart"), # Fails due to test framework issue. "google": Google("google"),
"github": Github("github"), "imgur": Imgur("imgur"),
"google": Google("google"), "instagram": Instagram("instagram"), # Iframe, pw saved but not autofilled.
"imgur": Imgur("imgur"), "linkedin": Linkedin("linkedin"),
"instagram": Instagram("instagram"), # Iframe, pw saved but not autofilled. "liveinternet": Liveinternet("liveinternet"),
"linkedin": Linkedin("linkedin"), "live": Live("live", username_not_auto=True), # http://crbug.com/367768
"liveinternet": Liveinternet("liveinternet"), "mailru": Mailru("mailru"),
"live": Live("live", username_not_auto=True), # http://crbug.com/367768 "nytimes": Nytimes("nytimes"),
"mailru": Mailru("mailru"), "odnoklassniki": Odnoklassniki("odnoklassniki"),
"nytimes": Nytimes("nytimes"), "pinterest": Pinterest("pinterest"),
"odnoklassniki": Odnoklassniki("odnoklassniki"), "reddit": Reddit("reddit", username_not_auto=True),
"pinterest": Pinterest("pinterest"), "stackexchange": StackExchange("stackexchange"), # Iframe, not autofilled.
"reddit": Reddit("reddit", username_not_auto=True), "tumblr": Tumblr("tumblr", username_not_auto=True),
"stackexchange": StackExchange("stackexchange"), # Iframe, not autofilled. "twitter": Twitter("twitter"),
"tumblr": Tumblr("tumblr", username_not_auto=True), "vkontakte": Vkontakte("vkontakte"),
"twitter": Twitter("twitter"), "vube": Vube("vube"), # http://crbug.com/368690
"vkontakte": Vkontakte("vkontakte"), "wikia": Wikia("wikia"),
"vube": Vube("vube"), # http://crbug.com/368690 "wikipedia": Wikipedia("wikipedia", username_not_auto=True),
"wikia": Wikia("wikia"), "wordpress": Wordpress("wordpress"),
"wikipedia": Wikipedia("wikipedia", username_not_auto=True), "yahoo": Yahoo("yahoo", username_not_auto=True),
"wordpress": Wordpress("wordpress"), "yandex": Yandex("yandex"),
"yahoo": Yahoo("yahoo", username_not_auto=True), "ziddu": Ziddu("ziddu"), # Password not saved.
"yandex": Yandex("yandex"), }
"ziddu": Ziddu("ziddu"), # Password not saved.
}
tests_to_run = tests_to_run or all_tests.keys()
for test_name in tests_to_run:
if test_name in all_tests.keys():
environment.AddWebsiteTest(all_tests[test_name])
def saveResults(environment_tests_results, environment_save_path): def saveResults(environment_tests_results, environment_save_path):
...@@ -515,12 +496,10 @@ def saveResults(environment_tests_results, environment_save_path): ...@@ -515,12 +496,10 @@ def saveResults(environment_tests_results, environment_save_path):
with open(environment_save_path, "w") as save_file: with open(environment_save_path, "w") as save_file:
save_file.write(xml) save_file.write(xml)
def RunTests(chrome_path, chromedriver_path, profile_path, def RunTest(chrome_path, chromedriver_path, profile_path,
environment_passwords_path, enable_automatic_password_saving, environment_passwords_path, enable_automatic_password_saving,
environment_numeric_level, log_to_console, environment_log_file, website_test_name):
environment_tested_websites, tests=None): """Runs the test for the specified website.
"""Runs the the tests
Args: Args:
chrome_path: The chrome binary file. chrome_path: The chrome binary file.
...@@ -529,42 +508,31 @@ def RunTests(chrome_path, chromedriver_path, profile_path, ...@@ -529,42 +508,31 @@ def RunTests(chrome_path, chromedriver_path, profile_path,
environment_passwords_path: The usernames and passwords file. environment_passwords_path: The usernames and passwords file.
enable_automatic_password_saving: If True, the passwords are going to be enable_automatic_password_saving: If True, the passwords are going to be
saved without showing the prompt. saved without showing the prompt.
environment_numeric_level: The log verbosity. website_test_name: Name of the website to test (refer to keys in
log_to_console: If True, the debug logs will be shown on the console. all_tests above).
environment_log_file: The file where to store the log. If it's empty, the
log is not stored.
environment_tested_websites: One of the TypeOfTestedWebsites values,
indicating which group of tests to run.
tests: Specifies which tests to run. Ignored unless
|environment_tested_websites| is equal to LIST_OF_TESTS.
Returns: Returns:
The results of tests as list of TestResults. The results of the test as list of TestResults.
Raises: Raises:
Exception: An exception is raised if one of the tests fails. Exception: An exception is raised if one of the tests for the website
fails, or if the website name is not known.
""" """
environment = Environment(chrome_path, chromedriver_path, profile_path, environment = Environment(chrome_path, chromedriver_path, profile_path,
environment_passwords_path, environment_passwords_path,
enable_automatic_password_saving, enable_automatic_password_saving)
environment_numeric_level,
log_to_console,
environment_log_file)
# Test which care about the save-password prompt need the prompt # Test which care about the save-password prompt need the prompt
# to be shown. Automatic password saving results in no prompt. # to be shown. Automatic password saving results in no prompt.
run_prompt_tests = not enable_automatic_password_saving run_prompt_tests = not enable_automatic_password_saving
Tests(environment, tests) if website_test_name in all_tests:
environment.AddWebsiteTest(all_tests[website_test_name])
if environment_tested_websites == TypeOfTestedWebsites.ALL_TESTS:
environment.AllTests(run_prompt_tests)
elif environment_tested_websites == TypeOfTestedWebsites.LIST_OF_TESTS:
environment.Test(tests, run_prompt_tests)
else: else:
raise Exception("Error: |environment_tested_websites| has to be one of the" raise Exception("Test name {} is unknown.".format(website_test_name))
"TypeOfTestedWebsites values")
environment.AllTests(run_prompt_tests)
environment.Quit() environment.Quit()
return environment.tests_results return environment.tests_results
...@@ -576,77 +544,46 @@ if __name__ == "__main__": ...@@ -576,77 +544,46 @@ if __name__ == "__main__":
parser.add_argument( parser.add_argument(
"--chrome-path", action="store", dest="chrome_path", "--chrome-path", action="store", dest="chrome_path",
help="Set the chrome path (required).", nargs=1, required=True) help="Set the chrome path (required).", required=True)
parser.add_argument( parser.add_argument(
"--chromedriver-path", action="store", dest="chromedriver_path", "--chromedriver-path", action="store", dest="chromedriver_path",
help="Set the chromedriver path (required).", nargs=1, required=True) help="Set the chromedriver path (required).", required=True)
parser.add_argument( parser.add_argument(
"--profile-path", action="store", dest="profile_path", "--profile-path", action="store", dest="profile_path",
help="Set the profile path (required). You just need to choose a " help="Set the profile path (required). You just need to choose a "
"temporary empty folder. If the folder is not empty all its content " "temporary empty folder. If the folder is not empty all its content "
"is going to be removed.", "is going to be removed.",
nargs=1, required=True) required=True)
parser.add_argument( parser.add_argument(
"--passwords-path", action="store", dest="passwords_path", "--passwords-path", action="store", dest="passwords_path",
help="Set the usernames/passwords path (required).", nargs=1, help="Set the usernames/passwords path (required).", required=True)
required=True) parser.add_argument("--save-path", action="store", dest="save_path",
parser.add_argument("--log", action="store", nargs=1, dest="log_level",
help="Set log level.")
parser.add_argument("--log-screen", action="store_true", dest="log_screen",
help="Show log on the screen.")
parser.add_argument("--log-file", action="store", dest="log_file",
help="Write the log in a file.", nargs=1)
parser.add_argument("--save-path", action="store", nargs=1, dest="save_path",
help="Write the results in a file.") help="Write the results in a file.")
parser.add_argument("tests", help="Tests to be run.", nargs="*") parser.add_argument("test", help="Test to be run.")
args = parser.parse_args() args = parser.parse_args()
passwords_path = args.passwords_path[0]
tested_websites = TypeOfTestedWebsites.ALL_TESTS
if args.tests:
tested_websites = TypeOfTestedWebsites.LIST_OF_TESTS
numeric_level = None
if args.log_level:
numeric_level = getattr(logging, args.log_level[0].upper(), None)
if not isinstance(numeric_level, int):
raise ValueError("Invalid log level: %s" % args.log_level[0])
log_file = None
if args.log_file:
log_file = args.log_file[0]
save_path = None save_path = None
if args.save_path: if args.save_path:
save_path = args.save_path[0] save_path = args.save_path
# Run the test without enable-automatic-password-saving to check whether or # Run the test without enable-automatic-password-saving to check whether or
# not the prompt is shown in the way we expected. # not the prompt is shown in the way we expected.
tests_results = RunTests(args.chrome_path[0], tests_results = RunTest(args.chrome_path,
args.chromedriver_path[0], args.chromedriver_path,
args.profile_path[0], args.profile_path,
passwords_path, args.passwords_path,
False, False,
numeric_level, args.test)
args.log_screen,
log_file,
tested_websites,
args.tests)
# Run the test with enable-automatic-password-saving to check whether or not # Run the test with enable-automatic-password-saving to check whether or not
# the passwords is stored in the the way we expected. # the passwords is stored in the the way we expected.
tests_results += RunTests(args.chrome_path[0], tests_results += RunTest(args.chrome_path,
args.chromedriver_path[0], args.chromedriver_path,
args.profile_path[0], args.profile_path,
passwords_path, args.passwords_path,
True, True,
numeric_level, args.test)
args.log_screen,
log_file,
tested_websites,
args.tests)
saveResults(tests_results, save_path) saveResults(tests_results, save_path)
...@@ -5,11 +5,8 @@ ...@@ -5,11 +5,8 @@
"""WebsiteTest testing class.""" """WebsiteTest testing class."""
import logging import logging
import sys
import time import time
sys.path.insert(0, '../../../../third_party/webdriver/pylib/')
from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
...@@ -135,16 +132,6 @@ class WebsiteTest: ...@@ -135,16 +132,6 @@ class WebsiteTest:
hover = ActionChains(self.driver).move_to_element(element) hover = ActionChains(self.driver).move_to_element(element)
hover.perform() hover.perform()
def SendEnterTo(self, selector):
"""Sends an enter key to an element.
Args:
selector: The element CSS selector.
"""
logging.info("action: SendEnterTo %s" % selector)
body = self.driver.find_element_by_tag_name("body")
body.send_keys(Keys.ENTER)
# Waiting/Displaying actions. # Waiting/Displaying actions.
def IsDisplayed(self, selector): def IsDisplayed(self, selector):
......
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