From c42d9a34beddcace535f090e6f011cb18bf0177f Mon Sep 17 00:00:00 2001 From: Mwindo Date: Thu, 13 Jul 2023 16:20:57 -0400 Subject: [PATCH 1/5] more thorough tests --- src/tests/SeleniumBrowser.php | 61 ++++++++++ src/tests/test_affiliate/WidgetsTest.php | 135 ++++++++++++++++++++--- 2 files changed, 181 insertions(+), 15 deletions(-) diff --git a/src/tests/SeleniumBrowser.php b/src/tests/SeleniumBrowser.php index c45e1c4a..781c2712 100644 --- a/src/tests/SeleniumBrowser.php +++ b/src/tests/SeleniumBrowser.php @@ -3,8 +3,10 @@ namespace Organic; use Exception; +use Facebook\WebDriver\Exception\WebDriverException; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\DesiredCapabilities; +use Facebook\WebDriver\WebDriverExpectedCondition; use Facebook\WebDriver\WebDriverWait; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\Chrome\ChromeOptions; @@ -16,6 +18,7 @@ const WP_LOGIN_URL = WP_HOME . '/wp-login.php'; const WP_NEW_POST_URL = WP_HOME . '/wp-admin/post-new.php'; +const WP_POSTS_HOME = WP_HOME . '/wp-admin/edit.php'; class SeleniumBrowser { @@ -316,4 +319,62 @@ function fillParagraphBlock( int $index = 0, string $text = '' ) { $this->fillTextInput( $this->getElementIfItExists( 'p', true )[$index], $text ); } + /** + * @return void + */ + function refreshPage(): void { + $this->driver->navigate()->refresh(); + } + + /** + * @return void + * @throws Exception + */ + function savePostDraft() { + $this->click( 'button[aria-label="Save draft"]' ); + // Once saved, we are redirected to a URL with a post ID. We wait for the save to complete. + $condition = WebDriverExpectedCondition::urlContains( 'post=' ); + $this->waitForCondition( $condition ); + } + + /** + * @return string + */ + function getCurrentPostID(): string + { + $url = $this->driver->getCurrentUrl(); + preg_match('/post=(\d+)(&|$)/', $url, $matches ); + return $matches[1]; + } + + /** + * Delete posts corresponding to post IDs. This assumes the posts are all on the front + * page of the WP Admin Post Editor list. + * @return void + * @throws Exception + */ + function deletePosts( array $postIDs ) { + $this->openPage( WP_POSTS_HOME ); + # Select all the posts to move to the trash. + foreach ( $postIDs as $postID ) { + $this->click( "#cb-select-{$postID}" ); + } + # Specify we want to move them to the trash. + $this->click('#bulk-action-selector-top' ); + $this->click('option[value="trash"]' ); + # Move them to the trash. + $this->click('#doaction' ); + # Now we need to go to the Trash tab. + $this->click('li.trash' ); + # Select all the posts to delete permanently. + foreach ( $postIDs as $postID ) { + $this->click( "#cb-select-{$postID}" ); + } + # Specify we want to delete them. + $this->click('#bulk-action-selector-top' ); + $this->click('option[value="delete"]' ); + # Delete them. + $this->click('#doaction' ); + } + } \ No newline at end of file diff --git a/src/tests/test_affiliate/WidgetsTest.php b/src/tests/test_affiliate/WidgetsTest.php index 32f2b788..ec276930 100644 --- a/src/tests/test_affiliate/WidgetsTest.php +++ b/src/tests/test_affiliate/WidgetsTest.php @@ -19,6 +19,22 @@ class WidgetsTest extends TestCase { * Note that these tests will fail for non-Gutenberg WordPress (< version 5). */ + // Keep track of posts created during the tests so that we can delete them on tear down. + static $postIDsToDelete = []; + + static function tearDownAfterClass(): void + { + $browser = SeleniumBrowser::getTestBrowser(); + try { + $browser->deletePosts( WidgetsTest::$postIDsToDelete ); + } catch ( Exception $e ) { + error_log( 'Failed to delete test posts: ' . $e->getMessage() ); + } finally { + $browser->quit(); + parent::tearDownAfterClass(); + } + } + function wordPressVersionTooLow() : bool { if ( !empty( WP_VERSION ) && intval( substr( WP_VERSION, 0, 1 ) ) < 5 ) { return true; @@ -26,7 +42,90 @@ function wordPressVersionTooLow() : bool { return false; } - function checkWidgetIsAvailable( $blockType ) { + /** + * When the user selects an Organic Affiliate widgets block, an IFrame with login fields will appear. + * This function logs the test user in so that we can customize and insert the widget. + * @param SeleniumBrowser $browser + * @return void + * @throws Exception + */ + private function logIntoWidgetsSelectionIFrame( SeleniumBrowser $browser ) { + $iframe = $browser->getOrganicIframe(); + $browser->switchToIframe( $iframe ); + # Log in with test account. + $browser->fillTextInput( '#email', ORGANIC_TEST_USER_EMAIL ); + $browser->fillTextInput( '#password', ORGANIC_TEST_USER_PASSWORD ); + $browser->click( '#signin-button' ); + } + + /** + * Search for and select the Test Product in our Organic Affiliate widgets IFrame. + * @param SeleniumBrowser $browser + * @return void + * @throws Exception + */ + private function selectTestProduct( SeleniumBrowser $browser ) { + $test_product_guid = TEST_PRODUCT_GUID; + $browser->fillTextInput( '#affiliate-product-search-entry', 'Selenium Test Product' ); + try { + $browser->click("[data-test-element=\"affiliate-product-select-{$test_product_guid}\"]" ); + } catch ( Exception $e ) { + $browser->quit(); + $this->fail( 'Was Organic Demo\'s Selenium Test Product deleted? ' . $e->getMessage() ); + } + } + + /** + * Search for and select the Test Product offer link in our Organic Affiliate widgets IFrame. + * @param SeleniumBrowser $browser + * @return void + * @throws Exception + */ + private function selectTestProductOfferLink( SeleniumBrowser $browser ) { + $browser->fillTextInput( '#affiliate-product-search-entry', 'Selenium Test Product' ); + $browser->click( '[data-test-element="affiliate-offer-button"]' ); + } + + /** + * Certain selectors are based on, e.g., product-card rather than organic-affiliate-product-card. + * This returns the shorter version. + * @param string $blockType + * @return false|string + */ + private function truncateBlockType( string $blockType ) { + return substr( $blockType, strlen('organic-affiliate-' ) ); + } + + /** + * Once the widget is customized as wanted, confirm and insert the widget into the editor. + * @param SeleniumBrowser $browser + * @param string $blockType + * @return void + * @throws Exception + */ + private function confirmWidgetSelection( SeleniumBrowser $browser, string $blockType ) { + $blockTypeTruncated = $this->truncateBlockType( $blockType ); + $browser->click( "[data-test-element=\"affiliate-create-{$blockTypeTruncated}-confirm\"]" ); + $browser->switchToDefaultContext(); + } + + /** + * Find and return the Organic Affiliate widgets iframe. + * @return mixed + * @throws Exception + */ + function getRenderedWidgetIFrame( SeleniumBrowser $browser, string $blockType ) { + $blockTypeTruncated = $this->truncateBlockType( $blockType ); + return $browser->waitFor( + "div[data-organic-affiliate-integration={$blockTypeTruncated}] > iframe", null + ); + } + + /** + * @param $blockType + * @return void + */ + function checkWidgetInsertion( $blockType ) { if ( $this->wordPressVersionTooLow() ) { $this->fail( 'WordPress version ' . WP_VERSION . ' does not support custom blocks.' ); } @@ -34,8 +133,20 @@ function checkWidgetIsAvailable( $blockType ) { try { $browser->goToNewPost(); $browser->addBlock( $blockType ); - // Check the Organic iframe appears. - $browser->getOrganicIframe(); + $this->logIntoWidgetsSelectionIFrame( $browser ); + $this->selectTestProduct( $browser ); + $this->confirmWidgetSelection( $browser, $blockType ); + # First, check that the widget is rendered (as an IFrame) upon insertion. + $this->getRenderedWidgetIFrame( $browser, $blockType ); + // Next, we'll check that the widget is still rendered after refreshing the page. + // To refresh the page, we need to save. + $browser->savePostDraft(); + // We mark the post for eventual deletion when we tear down the tests. + WidgetsTest::$postIDsToDelete[] = $browser->getCurrentPostID(); + $browser->refreshPage(); + // Check to see that the widget is rendered after refreshing the page. + $this->getRenderedWidgetIFrame( $browser, $blockType ); + $browser->quit(); $this->assertTrue( true ); } catch ( Exception $e ) { @@ -49,7 +160,7 @@ function checkWidgetIsAvailable( $blockType ) { * @group selenium_test */ public function testProductCardAvailable() { - $this->checkWidgetIsAvailable( 'organic-affiliate-product-card' ); + $this->checkWidgetInsertion( 'organic-affiliate-product-card' ); } /** @@ -57,7 +168,7 @@ public function testProductCardAvailable() { * @group selenium_test */ public function testProductCarouselAvailable() { - $this->checkWidgetIsAvailable( 'organic-affiliate-product-carousel' ); + $this->checkWidgetInsertion( 'organic-affiliate-product-carousel' ); } /** @@ -83,16 +194,10 @@ public function testInsertMagicLink() { $browser->click( '[aria-label="Organic Tools"]' ); // Then we click the submenu item. $browser->click( 'button[role="menuitem"]' ); - // The Organic iframe should appear. - $iframe = $browser->getOrganicIframe(); - $browser->switchToIframe( $iframe ); - # Log in with test account. - $browser->fillTextInput( '#email', ORGANIC_TEST_USER_EMAIL ); - $browser->fillTextInput( '#password', ORGANIC_TEST_USER_PASSWORD ); - $browser->click( '#signin-button' ); - # Search for the test product. - $browser->fillTextInput( '#affiliate-product-search-entry', 'Selenium Test Product' ); - $browser->click( '[data-test-element="affiliate-offer-button"]' ); + # The Organic IFrame should appear. We log in. + $this->logIntoWidgetsSelectionIFrame( $browser ); + # Then we search for and select the test product. + $this->selectTestProductOfferLink( $browser ); # We move out of the iframe and check that the link has been added for the test product. $browser->switchToDefaultContext(); $browser->wait(); From 0822636138b3468496f2c6283b5ca802127c4122 Mon Sep 17 00:00:00 2001 From: Mwindo Date: Thu, 13 Jul 2023 16:31:01 -0400 Subject: [PATCH 2/5] some cleanup --- src/tests/SeleniumBrowser.php | 3 +-- src/tests/test_affiliate/WidgetsTest.php | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/tests/SeleniumBrowser.php b/src/tests/SeleniumBrowser.php index 781c2712..b41c4635 100644 --- a/src/tests/SeleniumBrowser.php +++ b/src/tests/SeleniumBrowser.php @@ -3,7 +3,6 @@ namespace Organic; use Exception; -use Facebook\WebDriver\Exception\WebDriverException; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\WebDriverExpectedCondition; @@ -330,7 +329,7 @@ function refreshPage(): void { * @return void * @throws Exception */ - function savePostDraft() { + function savePostAsDraft() { $this->click( 'button[aria-label="Save draft"]' ); // Once saved, we are redirected to a URL with a post ID. We wait for the save to complete. $condition = WebDriverExpectedCondition::urlContains( 'post=' ); diff --git a/src/tests/test_affiliate/WidgetsTest.php b/src/tests/test_affiliate/WidgetsTest.php index ec276930..bf284b0d 100644 --- a/src/tests/test_affiliate/WidgetsTest.php +++ b/src/tests/test_affiliate/WidgetsTest.php @@ -31,7 +31,6 @@ static function tearDownAfterClass(): void error_log( 'Failed to delete test posts: ' . $e->getMessage() ); } finally { $browser->quit(); - parent::tearDownAfterClass(); } } @@ -44,7 +43,7 @@ function wordPressVersionTooLow() : bool { /** * When the user selects an Organic Affiliate widgets block, an IFrame with login fields will appear. - * This function logs the test user in so that we can customize and insert the widget. + * This function logs us in as the test user so that we can customize and insert the widget. * @param SeleniumBrowser $browser * @return void * @throws Exception @@ -140,7 +139,7 @@ function checkWidgetInsertion( $blockType ) { $this->getRenderedWidgetIFrame( $browser, $blockType ); // Next, we'll check that the widget is still rendered after refreshing the page. // To refresh the page, we need to save. - $browser->savePostDraft(); + $browser->savePostAsDraft(); // We mark the post for eventual deletion when we tear down the tests. WidgetsTest::$postIDsToDelete[] = $browser->getCurrentPostID(); $browser->refreshPage(); From f8e01daad44c42053c124b5d2683f1d1b403090a Mon Sep 17 00:00:00 2001 From: Mwindo Date: Thu, 13 Jul 2023 16:41:36 -0400 Subject: [PATCH 3/5] standardize comments --- src/tests/SeleniumBrowser.php | 14 +++++++------- src/tests/test_affiliate/WidgetsTest.php | 11 ++++++----- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/tests/SeleniumBrowser.php b/src/tests/SeleniumBrowser.php index b41c4635..d0449d76 100644 --- a/src/tests/SeleniumBrowser.php +++ b/src/tests/SeleniumBrowser.php @@ -354,25 +354,25 @@ function getCurrentPostID(): string */ function deletePosts( array $postIDs ) { $this->openPage( WP_POSTS_HOME ); - # Select all the posts to move to the trash. + // Select all the posts to move to the trash. foreach ( $postIDs as $postID ) { $this->click( "#cb-select-{$postID}" ); } - # Specify we want to move them to the trash. + // Specify we want to move them to the trash. $this->click('#bulk-action-selector-top' ); $this->click('option[value="trash"]' ); - # Move them to the trash. + // Move them to the trash. $this->click('#doaction' ); - # Now we need to go to the Trash tab. + // Now we need to go to the Trash tab. $this->click('li.trash' ); - # Select all the posts to delete permanently. + // Select all the posts to delete permanently. foreach ( $postIDs as $postID ) { $this->click( "#cb-select-{$postID}" ); } - # Specify we want to delete them. + // Specify we want to delete them. $this->click('#bulk-action-selector-top' ); $this->click('option[value="delete"]' ); - # Delete them. + // Delete them. $this->click('#doaction' ); } diff --git a/src/tests/test_affiliate/WidgetsTest.php b/src/tests/test_affiliate/WidgetsTest.php index bf284b0d..0c38ca6a 100644 --- a/src/tests/test_affiliate/WidgetsTest.php +++ b/src/tests/test_affiliate/WidgetsTest.php @@ -51,7 +51,7 @@ function wordPressVersionTooLow() : bool { private function logIntoWidgetsSelectionIFrame( SeleniumBrowser $browser ) { $iframe = $browser->getOrganicIframe(); $browser->switchToIframe( $iframe ); - # Log in with test account. + // Log in with test account. $browser->fillTextInput( '#email', ORGANIC_TEST_USER_EMAIL ); $browser->fillTextInput( '#password', ORGANIC_TEST_USER_PASSWORD ); $browser->click( '#signin-button' ); @@ -105,6 +105,7 @@ private function truncateBlockType( string $blockType ) { private function confirmWidgetSelection( SeleniumBrowser $browser, string $blockType ) { $blockTypeTruncated = $this->truncateBlockType( $blockType ); $browser->click( "[data-test-element=\"affiliate-create-{$blockTypeTruncated}-confirm\"]" ); + // The IFrame will disappear and the widget will be inserted, so we toggle Selenium out of the IFrame. $browser->switchToDefaultContext(); } @@ -135,7 +136,7 @@ function checkWidgetInsertion( $blockType ) { $this->logIntoWidgetsSelectionIFrame( $browser ); $this->selectTestProduct( $browser ); $this->confirmWidgetSelection( $browser, $blockType ); - # First, check that the widget is rendered (as an IFrame) upon insertion. + // First, check that the widget is rendered (as an IFrame) upon insertion. $this->getRenderedWidgetIFrame( $browser, $blockType ); // Next, we'll check that the widget is still rendered after refreshing the page. // To refresh the page, we need to save. @@ -193,11 +194,11 @@ public function testInsertMagicLink() { $browser->click( '[aria-label="Organic Tools"]' ); // Then we click the submenu item. $browser->click( 'button[role="menuitem"]' ); - # The Organic IFrame should appear. We log in. + // The Organic IFrame should appear. We log in. $this->logIntoWidgetsSelectionIFrame( $browser ); - # Then we search for and select the test product. + // Then we search for and select the test product. $this->selectTestProductOfferLink( $browser ); - # We move out of the iframe and check that the link has been added for the test product. + // We move out of the iframe and check that the link has been added for the test product. $browser->switchToDefaultContext(); $browser->wait(); $guid = TEST_PRODUCT_GUID; From 885adc4c4e78fc165b5cdbaad90428442dd95fdb Mon Sep 17 00:00:00 2001 From: Mwindo Date: Thu, 13 Jul 2023 16:46:12 -0400 Subject: [PATCH 4/5] fixing some styling --- src/tests/SeleniumBrowser.php | 14 +++++++------- src/tests/test_affiliate/WidgetsTest.php | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/tests/SeleniumBrowser.php b/src/tests/SeleniumBrowser.php index d0449d76..8692dd25 100644 --- a/src/tests/SeleniumBrowser.php +++ b/src/tests/SeleniumBrowser.php @@ -342,7 +342,7 @@ function savePostAsDraft() { function getCurrentPostID(): string { $url = $this->driver->getCurrentUrl(); - preg_match('/post=(\d+)(&|$)/', $url, $matches ); + preg_match( '/post=(\d+)(&|$)/', $url, $matches ); return $matches[1]; } @@ -359,21 +359,21 @@ function deletePosts( array $postIDs ) { $this->click( "#cb-select-{$postID}" ); } // Specify we want to move them to the trash. - $this->click('#bulk-action-selector-top' ); + $this->click( '#bulk-action-selector-top' ); $this->click('option[value="trash"]' ); // Move them to the trash. - $this->click('#doaction' ); + $this->click( '#doaction' ); // Now we need to go to the Trash tab. - $this->click('li.trash' ); + $this->click( 'li.trash' ); // Select all the posts to delete permanently. foreach ( $postIDs as $postID ) { $this->click( "#cb-select-{$postID}" ); } // Specify we want to delete them. - $this->click('#bulk-action-selector-top' ); - $this->click('option[value="delete"]' ); + $this->click( '#bulk-action-selector-top' ); + $this->click( 'option[value="delete"]' ); // Delete them. - $this->click('#doaction' ); + $this->click( '#doaction' ); } } \ No newline at end of file diff --git a/src/tests/test_affiliate/WidgetsTest.php b/src/tests/test_affiliate/WidgetsTest.php index 0c38ca6a..e5f55e4b 100644 --- a/src/tests/test_affiliate/WidgetsTest.php +++ b/src/tests/test_affiliate/WidgetsTest.php @@ -159,7 +159,7 @@ function checkWidgetInsertion( $blockType ) { * Test that the product card block is available. * @group selenium_test */ - public function testProductCardAvailable() { + public function testProductCard() { $this->checkWidgetInsertion( 'organic-affiliate-product-card' ); } @@ -167,7 +167,7 @@ public function testProductCardAvailable() { * Test that the product carousel block is available. * @group selenium_test */ - public function testProductCarouselAvailable() { + public function testProductCarousel() { $this->checkWidgetInsertion( 'organic-affiliate-product-carousel' ); } From 9dfee3463112109822c0fbf5ea33f42b692131c1 Mon Sep 17 00:00:00 2001 From: Mwindo Date: Thu, 13 Jul 2023 16:54:00 -0400 Subject: [PATCH 5/5] add small wait before checking for iframe --- src/tests/test_affiliate/WidgetsTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tests/test_affiliate/WidgetsTest.php b/src/tests/test_affiliate/WidgetsTest.php index e5f55e4b..ebc5760d 100644 --- a/src/tests/test_affiliate/WidgetsTest.php +++ b/src/tests/test_affiliate/WidgetsTest.php @@ -67,7 +67,7 @@ private function selectTestProduct( SeleniumBrowser $browser ) { $test_product_guid = TEST_PRODUCT_GUID; $browser->fillTextInput( '#affiliate-product-search-entry', 'Selenium Test Product' ); try { - $browser->click("[data-test-element=\"affiliate-product-select-{$test_product_guid}\"]" ); + $browser->click( "[data-test-element=\"affiliate-product-select-{$test_product_guid}\"]" ); } catch ( Exception $e ) { $browser->quit(); $this->fail( 'Was Organic Demo\'s Selenium Test Product deleted? ' . $e->getMessage() ); @@ -116,6 +116,7 @@ private function confirmWidgetSelection( SeleniumBrowser $browser, string $block */ function getRenderedWidgetIFrame( SeleniumBrowser $browser, string $blockType ) { $blockTypeTruncated = $this->truncateBlockType( $blockType ); + $browser->wait(); return $browser->waitFor( "div[data-organic-affiliate-integration={$blockTypeTruncated}] > iframe", null );