public inbox for [email protected]  
help / color / mirror / Atom feed
From: Atira Odhner <[email protected]>
To: Dave Page <[email protected]>
Cc: George Gelashvili <[email protected]>
Cc: pgadmin-hackers <[email protected]>
Subject: Re: Acceptance Tests against a browser (WIP)
Date: Thu, 9 Feb 2017 11:17:40 -0500
Message-ID: <CA+Vc24qwf1iDiJ9aQDWq1bDHqgsAWbTQ4Y9MkLw+SRhrPtuXgA@mail.gmail.com> (raw)
In-Reply-To: <CA+OCxoxvhPXStN2VLsdHAgetGwT3aSQDhf3c5eAgYh=KqOGSbw@mail.gmail.com>
References: <CA+Vc24rPt8BGgtZM5Pv4CARpw7t42A5aqQX5q0_QyWWOfgsGTA@mail.gmail.com>
	<CAHowoHbyxYrnROe+8BDBaf_Z8KneW95be8g-5OMZ4q5jFH28Ow@mail.gmail.com>
	<CA+OCxoyNGb4A6BLV=u4gBCCBzj+HT_5GKPmnmu91WAGrpkEZtg@mail.gmail.com>
	<CAHowoHYNQC5kovTvkVoYWRr6NCDvLY-8PsEMcJLsu-7z43VNeg@mail.gmail.com>
	<CA+OCxoy+miWFpGa6W=1t7swn8vPvDayKtNdGnYUdaYOPNEkQ2A@mail.gmail.com>
	<CAHowoHYZxQxVLxA0N8iKoVGN=0vahSxrc0Kd4LkqoOWQYUSmkQ@mail.gmail.com>
	<CAHowoHa4k7E6LBDaiuUFPBKOcsa_ML7M3KEhDZUsbR-KKujemw@mail.gmail.com>
	<CA+OCxow6adDHsG2VTtVbRQ2ybCOdKTanFGf1hNoVdn_Gt9Betw@mail.gmail.com>
	<CA+OCxozeqDfoAiJ4AYf-Hnoq5FTCwWVXcEE-h460H5-_RVg9jw@mail.gmail.com>
	<CAHowoHa32=Y8zjYjZ_DHLukxHFzJZ9kV6y1ErDUwfPaE5MNCrw@mail.gmail.com>
	<CA+Vc24rpjYertAeAqK0T5eXAe=oGOCg2y09gNshgamih1KrOMg@mail.gmail.com>
	<CA+OCxozuy8DMJq79FTmk5p86jW-Z_88i85Wx-yc2OYLg=FTznQ@mail.gmail.com>
	<CAHowoHbXL2wu2feErTTWhgmcM4=48KATZkvguGRiy-UNNW-mfQ@mail.gmail.com>
	<CA+OCxoymnnPU5N5VU37xNWBGLZUSrSshwQSVVy1ut++YqT6UQQ@mail.gmail.com>
	<CA+OCxoxZMuXOPiNEMbYCBPXaZYBquRpcNd2YpvfwbS1T8bUPzg@mail.gmail.com>
	<CA+Vc24pjFs=Qy4-eFq5bWUAzRfwZkictJ0GykxbNh3hz=1YcBw@mail.gmail.com>
	<CA+OCxozt9evQcAtsLhRvO0xYL7QKr6Gk=RMUzTJ_w0HfFYtVww@mail.gmail.com>
	<CA+Vc24r=B7+2nEMquJde0cCwRS66oEdQhB2BDAkMDzdGtPGtjg@mail.gmail.com>
	<CA+Vc24ornL=p=eepG+MOt2wAX+A29rhJyMYq-tgHkwLk0F40DQ@mail.gmail.com>
	<CA+OCxozsTtd+dTRV7=N+xD-0fD4_fPAq+iFrORPk3QU2mHo6UA@mail.gmail.com>
	<CA+Vc24p_Q9FGy3MGCHUd_HaGfHNmqkfqrqRV9HSkS7cSosTdFA@mail.gmail.com>
	<CA+OCxowRnWpS5sBRWFdL=cprJ4rU_bencj2VpbLx4YjtJg-TMQ@mail.gmail.com>
	<CA+Vc24rG9rERsphieMhGTPLSCajX=DUV2T_0rB6tK+WYz+V=+A@mail.gmail.com>
	<CA+OCxoxvhPXStN2VLsdHAgetGwT3aSQDhf3c5eAgYh=KqOGSbw@mail.gmail.com>
List-Unsubscribe:  <mailto:[email protected]?body=unsub%20pgadmin-hackers>

Hi Dave,

 I think the problem was that the way you phrased it,


You're right, we totally messed that up. We were talking about making 3
patches and ended up making only 2 and forgot to reword that bit.
Sorry about that.

Here are the two patches for this change that resolves the AttributeError you
were seeing. The first patch is identical to the patch of the same name in
the other email thread.

We're used to
> dealing with larger patchsets via the mailing list - typically as long
> as you're clear about any dependencies, it shouldn't be a problem.
>

Great! We'll try sending patchsets from now on and hopefully that resolves
some of the issues we were seeing.

Tira & George

On Thu, Feb 9, 2017 at 9:28 AM, Dave Page <[email protected]> wrote:

> Hi
>
> On Thu, Feb 9, 2017 at 2:20 PM, Atira Odhner <[email protected]> wrote:
> > Certainly.  We did mention the dependency in the email. Would it be
> better
> > to mention it in the patch name?
>
> I think the problem was that the way you phrased it, it sounded
> optional ("an updated patch which does not include adding that test
> helper in case you apply the show-tables patch first"). I think a
> clear "This patch is dependent on patch Foo" would suffice.
>
> > Is there a better way for us to manage
> > these changes? On other open source projects, I've seen github mirrors
> set
> > up so that changes can be pulled in like branches rather then as patch
> > applies. That would have avoided this situation since the parent commit
> > would be pulled in with the same SHA from either pull request branch and
> git
> > would not see it as a conflict.
> >
> > I'm rather new to dealing with patch files like this so I would love some
> > tips.
>
> The Postgres project in general is quite conservative and stuck in
> it's ways about how things are done (which is usually a good thing
> considering you trust your data to the resulting code). We're used to
> dealing with larger patchsets via the mailing list - typically as long
> as you're clear about any dependencies, it shouldn't be a problem.
> Some of us use tools like PyCharms for handling patches and helping
> with reviews etc. which I guess replaces most, if not all of the
> GitHub functionality over plain git.
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [application/octet-stream] 0001-Add-create_table-to-test_utils.patch (1.6K, 3-0001-Add-create_table-to-test_utils.patch)
  download | inline diff:
From 75980f10b4cfe7cbabe62893a6ccf3ccc26949ff Mon Sep 17 00:00:00 2001
From: George Gelashvili and Tira Odhner <[email protected]>
Date: Wed, 8 Feb 2017 09:45:49 -0500
Subject: [PATCH 1/3] Add create_table to test_utils

---
 web/regression/test_utils.py | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/web/regression/test_utils.py b/web/regression/test_utils.py
index 1f9f0522..2dbf47bb 100644
--- a/web/regression/test_utils.py
+++ b/web/regression/test_utils.py
@@ -134,6 +134,25 @@ def create_database(server, db_name):
         traceback.print_exc(file=sys.stderr)
 
 
+def create_table(server, db_name, table_name):
+    try:
+        connection = get_db_connection(db_name,
+                                       server['username'],
+                                       server['db_password'],
+                                       server['host'],
+                                       server['port'])
+        old_isolation_level = connection.isolation_level
+        connection.set_isolation_level(0)
+        pg_cursor = connection.cursor()
+        pg_cursor.execute('''CREATE TABLE "%s" (name VARCHAR, value NUMERIC)''' % table_name)
+        pg_cursor.execute('''INSERT INTO "%s" VALUES ('Some-Name', 6)''' % table_name)
+        connection.set_isolation_level(old_isolation_level)
+        connection.commit()
+
+    except Exception:
+        traceback.print_exc(file=sys.stderr)
+
+
 def drop_database(connection, database_name):
     """This function used to drop the database"""
     if database_name not in ["postgres", "template1", "template0"]:
-- 
2.11.0



  [application/octet-stream] 0002-Acceptance-tests-with-input-fix.patch (17.1K, 4-0002-Acceptance-tests-with-input-fix.patch)
  download | inline diff:
commit f340178c3f2d779ea5368f48a36f47112e2fa316
Author: George Gelashvili and Tira Odhner <[email protected]>
Date:   Wed Feb 8 14:52:32 2017 -0500

    ACCEPTANCE TEST SQUASH
    
    Based on Work around input validation by sending backspace to clear

diff --git a/requirements_py2.txt b/requirements_py2.txt
index 4fb05891..998cdabf 100644
--- a/requirements_py2.txt
+++ b/requirements_py2.txt
@@ -36,6 +36,7 @@ testscenarios==0.5.0
 testtools==2.0.0
 traceback2==1.4.0
 unittest2==1.1.0
+selenium==3.0.2
 Werkzeug==0.9.6
 WTForms==2.0.2
 sqlparse==0.1.19
diff --git a/requirements_py3.txt b/requirements_py3.txt
index c4490f52..2239de63 100644
--- a/requirements_py3.txt
+++ b/requirements_py3.txt
@@ -35,6 +35,7 @@ testscenarios==0.5.0
 testtools==2.0.0
 traceback2==1.4.0
 unittest2==1.1.0
+selenium==3.0.2
 Werkzeug==0.9.6
 WTForms==2.0.2
 sqlparse==0.1.19
diff --git a/web/pgAdmin4.py b/web/pgAdmin4.py
index 68848c00..95e675b7 100644
--- a/web/pgAdmin4.py
+++ b/web/pgAdmin4.py
@@ -59,6 +59,12 @@ if 'PGADMIN_PORT' in globals():
                      globals()['PGADMIN_PORT'])
     server_port = int(globals()['PGADMIN_PORT'])
     PGADMIN_RUNTIME = True
+elif 'PGADMIN_PORT' in os.environ:
+    port = os.environ['PGADMIN_PORT']
+    app.logger.debug(
+        'Not running under the desktop runtime, port: %s',
+        port)
+    server_port = int(port)
 else:
     app.logger.debug(
         'Not running under the desktop runtime, port: %s',
diff --git a/web/pgadmin/acceptance/__init__.py b/web/pgadmin/acceptance/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/web/pgadmin/acceptance/tests/__init__.py b/web/pgadmin/acceptance/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py b/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py
new file mode 100644
index 00000000..c3bee4e6
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py
@@ -0,0 +1,92 @@
+#############################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2017, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##############################################################
+
+from selenium import webdriver
+from selenium.webdriver import ActionChains
+
+import config as app_config
+from pgadmin.utils.route import BaseTestGenerator
+from regression import test_utils
+from regression.utils.app_starter import AppStarter
+from regression.utils.pgadmin_page import PgadminPage
+
+
+class ConnectsToServerFeatureTest(BaseTestGenerator):
+    """
+    Tests that a database connection can be created from the UI
+    """
+
+    def setUp(self):
+        if app_config.SERVER_MODE:
+            self.skipTest("Currently, config is set to start pgadmin in server mode. "
+                          "This test doesn't know username and password so doesn't work in server mode")
+
+        driver = webdriver.Chrome()
+        self.app_starter = AppStarter(driver, app_config)
+        self.page = PgadminPage(driver, app_config)
+
+        connection = test_utils.get_db_connection(self.server['db'],
+                                                  self.server['username'],
+                                                  self.server['db_password'],
+                                                  self.server['host'],
+                                                  self.server['port'])
+        test_utils.drop_database(connection, "acceptance_test_db")
+        test_utils.create_database(self.server, "acceptance_test_db")
+        test_utils.create_table(self.server, "acceptance_test_db", "test_table")
+
+        self.app_starter.start_app()
+        self.page.wait_for_app()
+
+    def runTest(self):
+        self.assertEqual(app_config.APP_NAME, self.page.driver.title)
+        self.page.wait_for_spinner_to_disappear()
+
+        self._connects_to_server()
+        self._tables_node_expandable()
+
+    def tearDown(self):
+        self.page.remove_server(self.server)
+        self.app_starter.stop_app()
+
+        connection = test_utils.get_db_connection(self.server['db'],
+                                                  self.server['username'],
+                                                  self.server['db_password'],
+                                                  self.server['host'],
+                                                  self.server['port'])
+        test_utils.drop_database(connection, "acceptance_test_db")
+
+    def failureException(self, *args, **kwargs):
+        self.page.driver.save_screenshot('/tmp/pgadmin_connect_to_server_test_failure.png')
+        return AssertionError(*args, **kwargs)
+
+    def _connects_to_server(self):
+        self.page.find_by_xpath("//*[@class='aciTreeText' and .='Servers']").click()
+        self.page.driver.find_element_by_link_text("Object").click()
+        ActionChains(self.page.driver) \
+            .move_to_element(self.page.driver.find_element_by_link_text("Create")) \
+            .perform()
+        self.page.find_by_partial_link_text("Server...").click()
+
+        server_config = self.server
+        self.page.fill_input_by_field_name("name", server_config['name'])
+        self.page.find_by_partial_link_text("Connection").click()
+        self.page.fill_input_by_field_name("host", server_config['host'])
+        self.page.fill_input_by_field_name("port", server_config['port'])
+        self.page.fill_input_by_field_name("username", server_config['username'])
+        self.page.fill_input_by_field_name("password", server_config['db_password'])
+        self.page.find_by_xpath("//button[contains(.,'Save')]").click()
+
+    def _tables_node_expandable(self):
+        self.page.toggle_open_tree_item(self.server['name'])
+        self.page.toggle_open_tree_item('Databases')
+        self.page.toggle_open_tree_item('acceptance_test_db')
+        self.page.toggle_open_tree_item('Schemas')
+        self.page.toggle_open_tree_item('public')
+        self.page.toggle_open_tree_item('Tables')
+        self.page.toggle_open_tree_item('test_table')
diff --git a/web/pgadmin/acceptance/tests/template_selection_feature_test.py b/web/pgadmin/acceptance/tests/template_selection_feature_test.py
new file mode 100644
index 00000000..b7405d56
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/template_selection_feature_test.py
@@ -0,0 +1,78 @@
+from selenium import webdriver
+from selenium.webdriver import ActionChains
+
+import config as app_config
+from pgadmin.utils.route import BaseTestGenerator
+from regression import test_utils
+from regression.utils.app_starter import AppStarter
+from regression.utils.pgadmin_page import PgadminPage
+
+
+class TemplateSelectionFeatureTest(BaseTestGenerator):
+    def setUp(self):
+        if app_config.SERVER_MODE:
+            self.skipTest("Currently, config is set to start pgadmin in server mode. "
+                          "This test doesn't know username and password so doesn't work in server mode")
+
+        driver = webdriver.Chrome()
+        self.app_starter = AppStarter(driver, app_config)
+        self.page = PgadminPage(driver, app_config)
+
+        connection = test_utils.get_db_connection(self.server['db'],
+                                                  self.server['username'],
+                                                  self.server['db_password'],
+                                                  self.server['host'],
+                                                  self.server['port'])
+        test_utils.drop_database(connection, "acceptance_test_db")
+
+        test_utils.create_database(self.server, "acceptance_test_db")
+
+        self.app_starter.start_app()
+        self.page.wait_for_app()
+
+        self.page.add_server(self.server)
+
+    def runTest(self):
+        test_utils.create_table(self.server, "acceptance_test_db", "test_table")
+
+        self.page.toggle_open_tree_item(self.server['name'])
+        self.page.toggle_open_tree_item('Databases')
+        self.page.toggle_open_tree_item('acceptance_test_db')
+        self.page.toggle_open_tree_item('Schemas')
+        self.page.toggle_open_tree_item('public')
+        self.page.find_by_xpath("//*[@id='tree']//*[@class='aciTreeText' and .='Trigger Functions']").click()
+        self.page.find_by_partial_link_text("Object").click()
+        ActionChains(self.page.driver) \
+            .move_to_element(self.page.driver.find_element_by_link_text("Create")) \
+            .perform()
+        self.page.find_by_partial_link_text("Trigger function...").click()
+        self.page.fill_input_by_field_name("name", "test-trigger-function")
+        self.page.find_by_partial_link_text("Definition").click()
+        self.page.fill_codemirror_area_with(
+"""CREATE OR REPLACE FUNCTION log_last_name_changes()
+RETURNS TRIGGER AS
+$BODY$
+BEGIN
+
+END;
+$BODY$
+"""
+        )
+        self.page.find_by_partial_link_text("SQL").click()
+
+        self.page.find_by_xpath("//*[contains(@class,'CodeMirror-lines') and contains(.,'LEAKPROOF')]")
+
+    def tearDown(self):
+        self.page.find_by_xpath("//button[contains(.,'Cancel')]").click()
+        self.page.remove_server(self.server)
+        self.app_starter.stop_app()
+        connection = test_utils.get_db_connection(self.server['db'],
+                                                  self.server['username'],
+                                                  self.server['db_password'],
+                                                  self.server['host'],
+                                                  self.server['port'])
+        test_utils.drop_database(connection, "acceptance_test_db")
+
+    def failureException(self, *args, **kwargs):
+        self.page.driver.save_screenshot('/tmp/pgadmin_sql_template_selection_failure.png')
+        return AssertionError(*args, **kwargs)
diff --git a/web/regression/.gitignore b/web/regression/.gitignore
index 0581810b..723fce7e 100644
--- a/web/regression/.gitignore
+++ b/web/regression/.gitignore
@@ -1,4 +1,5 @@
 parent_id.pkl
 regression.log
+test_greenplum_config.json
 test_advanced_config.json
 test_config.json
diff --git a/web/regression/README b/web/regression/README
index 8cc29987..ae5d268d 100644
--- a/web/regression/README
+++ b/web/regression/README
@@ -103,6 +103,10 @@ Test Data Details
 Execution:
 -----------
 
+- For acceptance tests to run as part of the entire test suite, Chrome and chromedriver need to be installed:
+  get chromedriver from https://sites.google.com/a/chromium.org/chromedriver/downloads or a package manager
+  and make sure it is on the PATH
+
 - The test framework is modular and pluggable and dynamically locates tests
   for modules which are discovered at runtime. All test cases are found
   and registered automatically by its module name in
diff --git a/web/regression/utils/app_starter.py b/web/regression/utils/app_starter.py
new file mode 100644
index 00000000..b297bd6d
--- /dev/null
+++ b/web/regression/utils/app_starter.py
@@ -0,0 +1,34 @@
+import os
+import subprocess
+
+import signal
+
+import random
+
+class AppStarter:
+    """
+    Helper for starting the full pgadmin4 app and loading the page via selenium
+    """
+
+    def __init__(self, driver, app_config):
+        self.driver = driver
+        self.app_config = app_config
+
+    def start_app(self):
+        random_server_port = str(random.randint(10000, 65535))
+        env = {"PGADMIN_PORT": random_server_port}
+        env.update(os.environ)
+
+        self.pgadmin_process = subprocess.Popen(["python", "pgAdmin4.py", "magic-portal", random_server_port],
+                                                shell=False,
+                                                preexec_fn=os.setsid,
+                                                stderr=open(os.devnull, 'w'),
+                                                env=env)
+
+        self.driver.set_window_size(1024, 1024)
+        print("opening browser")
+        self.driver.get("http://" + self.app_config.DEFAULT_SERVER + ":" + random_server_port)
+
+    def stop_app(self):
+        self.driver.close()
+        os.killpg(os.getpgid(self.pgadmin_process.pid), signal.SIGTERM)
diff --git a/web/regression/utils/pgadmin_page.py b/web/regression/utils/pgadmin_page.py
new file mode 100644
index 00000000..d6a5836c
--- /dev/null
+++ b/web/regression/utils/pgadmin_page.py
@@ -0,0 +1,120 @@
+import time
+from selenium.common.exceptions import NoSuchElementException
+from selenium.webdriver import ActionChains
+from selenium.webdriver.common.keys import Keys
+
+
+class PgadminPage:
+    """
+    Helper class for interacting with the page, given a selenium driver
+    """
+    def __init__(self, driver, app_config):
+        self.driver = driver
+        self.app_config = app_config
+
+    def add_server(self, server_config):
+        self.wait_for_spinner_to_disappear()
+
+        self.find_by_xpath("//*[@class='aciTreeText' and contains(.,'Servers')]").click()
+        self.driver.find_element_by_link_text("Object").click()
+        ActionChains(self.driver) \
+            .move_to_element(self.driver.find_element_by_link_text("Create")) \
+            .perform()
+        self.find_by_partial_link_text("Server...").click()
+
+        self.fill_input_by_field_name("name", server_config['name'])
+        self.find_by_partial_link_text("Connection").click()
+        self.fill_input_by_field_name("host", server_config['host'])
+        self.fill_input_by_field_name("port", server_config['port'])
+        self.fill_input_by_field_name("username", server_config['username'])
+        self.fill_input_by_field_name("password", server_config['db_password'])
+        self.find_by_xpath("//button[contains(.,'Save')]").click()
+
+        self.find_by_xpath("//*[@id='tree']//*[.='" + server_config['name'] + "']")
+
+    def remove_server(self, server_config):
+        self.find_by_xpath("//*[@id='tree']//*[.='" + server_config['name'] + "' and @class='aciTreeItem']").click()
+        self.find_by_partial_link_text("Object").click()
+        self.find_by_partial_link_text("Delete/Drop").click()
+        time.sleep(0.5)
+        self.find_by_xpath("//button[contains(.,'OK')]").click()
+
+    def toggle_open_tree_item(self, tree_item_text):
+        self.find_by_xpath("//*[@id='tree']//*[.='" + tree_item_text + "']/../*[@class='aciTreeButton']").click()
+
+    def find_by_xpath(self, xpath):
+        return self.wait_for_element(lambda: self.driver.find_element_by_xpath(xpath))
+
+    def find_by_id(self, element_id):
+        return self.wait_for_element(lambda: self.driver.find_element_by_id(element_id))
+
+    def find_by_partial_link_text(self, link_text):
+        return self.wait_for_element(lambda: self.driver.find_element_by_partial_link_text(link_text))
+
+    def fill_input_by_field_name(self, field_name, field_content):
+        field = self.find_by_xpath("//input[@name='" + field_name + "']")
+        backspaces = [Keys.BACKSPACE]*len(field.get_attribute('value'))
+
+        field.click()
+        field.send_keys(backspaces)
+        field.send_keys(str(field_content))
+        self.wait_for_input_field_content(field_name, field_content)
+
+    def fill_codemirror_area_with(self, field_content):
+        self.find_by_xpath(
+            "//pre[contains(@class,'CodeMirror-line')]/../../../*[contains(@class,'CodeMirror-code')]").click()
+        ActionChains(self.driver).send_keys(field_content).perform()
+
+    def wait_for_input_field_content(self, field_name, content):
+        def input_field_has_content():
+            element = self.driver.find_element_by_xpath(
+                "//input[@name='" + field_name + "']")
+
+            return str(content) == element.get_attribute('value')
+
+        return self._wait_for("field to contain '" + str(content) + "'", input_field_has_content)
+
+    def wait_for_element(self, find_method_with_args):
+        def element_if_it_exists():
+            try:
+                element = find_method_with_args()
+                if element.is_displayed() & element.is_enabled():
+                    return element
+            except NoSuchElementException:
+                return False
+
+        return self._wait_for("element to exist", element_if_it_exists)
+
+    def wait_for_spinner_to_disappear(self):
+        def spinner_has_disappeared():
+            try:
+                self.driver.find_element_by_id("pg-spinner")
+                return False
+            except NoSuchElementException:
+                return True
+
+        self._wait_for("spinner to disappear", spinner_has_disappeared)
+
+    def wait_for_app(self):
+        def page_shows_app():
+            if self.driver.title == self.app_config.APP_NAME:
+                return True
+            else:
+                self.driver.refresh()
+                return False
+
+        self._wait_for("app to start", page_shows_app)
+
+    def _wait_for(self, waiting_for_message, condition_met_function):
+        timeout = 10
+        time_waited = 0
+        sleep_time = 0.01
+
+        while time_waited < timeout:
+            result = condition_met_function()
+            if result:
+                return result
+            time_waited += sleep_time
+            time.sleep(sleep_time)
+
+        raise RuntimeError("timed out waiting for " + waiting_for_message)


view thread (30+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected]
  Subject: Re: Acceptance Tests against a browser (WIP)
  In-Reply-To: <CA+Vc24qwf1iDiJ9aQDWq1bDHqgsAWbTQ4Y9MkLw+SRhrPtuXgA@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox