diff --git a/src/tests/SeleniumBrowser.php b/src/tests/SeleniumBrowser.php index c45e1c4a..8692dd25 100644 --- a/src/tests/SeleniumBrowser.php +++ b/src/tests/SeleniumBrowser.php @@ -5,6 +5,7 @@ use Exception; 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 +17,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 +318,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 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=' ); + $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..ebc5760d 100644 --- a/src/tests/test_affiliate/WidgetsTest.php +++ b/src/tests/test_affiliate/WidgetsTest.php @@ -19,6 +19,21 @@ 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(); + } + } + function wordPressVersionTooLow() : bool { if ( !empty( WP_VERSION ) && intval( substr( WP_VERSION, 0, 1 ) ) < 5 ) { return true; @@ -26,7 +41,92 @@ 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 us in as the test user 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\"]" ); + // The IFrame will disappear and the widget will be inserted, so we toggle Selenium out of the IFrame. + $browser->switchToDefaultContext(); + } + + /** + * Find and return the Organic Affiliate widgets iframe. + * @return mixed + * @throws Exception + */ + function getRenderedWidgetIFrame( SeleniumBrowser $browser, string $blockType ) { + $blockTypeTruncated = $this->truncateBlockType( $blockType ); + $browser->wait(); + 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 +134,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->savePostAsDraft(); + // 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 ) { @@ -48,16 +160,16 @@ function checkWidgetIsAvailable( $blockType ) { * Test that the product card block is available. * @group selenium_test */ - public function testProductCardAvailable() { - $this->checkWidgetIsAvailable( 'organic-affiliate-product-card' ); + public function testProductCard() { + $this->checkWidgetInsertion( 'organic-affiliate-product-card' ); } /** * Test that the product carousel block is available. * @group selenium_test */ - public function testProductCarouselAvailable() { - $this->checkWidgetIsAvailable( 'organic-affiliate-product-carousel' ); + public function testProductCarousel() { + $this->checkWidgetInsertion( 'organic-affiliate-product-carousel' ); } /** @@ -83,17 +195,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. - $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"]' ); - # We move out of the iframe and check that the link has been added for the test product. + // 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(); $guid = TEST_PRODUCT_GUID;