amOnPage('/wp-login.php'); if ($i->loadSessionSnapshot('login')) { return; } // remove any other WP auth & login cookies to avoid login/logout errors $authCookies = $i->grabCookiesWithPattern('/^wordpress_[a-z0-9]{32}$/') ?: []; $loginCookies = $i->grabCookiesWithPattern('/^wordpress_logged_in_[a-z0-9]{32}$/') ?: []; foreach (array_merge($authCookies, $loginCookies) as $cookie) { $i->resetCookie($cookie->getName()); } $i->wait(1);// this needs to be here, Username is not filled properly without this line $i->fillField('Username', 'admin'); $i->fillField('Password', 'password'); $i->click('Log In'); $i->waitForText('MailPoet', 10); $i->saveSessionSnapshot('login'); } /** * Define custom actions here */ public function logOut() { $i = $this; $i->amOnPage('/wp-login.php?action=logout'); $i->click('log out'); $i->waitForText('You are now logged out.'); $i->deleteSessionSnapshot('login'); } /** * Navigate to the specified MailPoet page in the admin. * * @param string $page The page to visit e.g. Inbox or Status */ public function amOnMailpoetPage($page) { $i = $this; if ($page === 'Emails') { $path = 'newsletters'; } else { $path = strtolower($page); } $i->amOnPage("/wp-admin/admin.php?page=mailpoet-$path"); } /** * Navigate to Mailhog page and wait for angular to load */ public function amOnMailboxAppPage() { $i = $this; $i->amOnUrl(self::MAIL_URL); // ensure that angular is loaded by checking angular specific class $i->waitForElement('.messages.ng-scope'); } /** * Define custom actions here */ public function amOnSiteHomepage() { $i = $this; $i->amOnUrl(self::WP_URL); } /** * Clear the Mailbox so it's empty */ public function emptyMailbox() { exec('rm -rf ' . self::MAILHOG_DATA_PATH . '/*', $output); } public function clickItemRowActionByItemName($itemName, $link) { $i = $this; for ($x = 1; $x <= 3; $x++) { try { $itemNameCellXpath = ['xpath' => '//tr//*[text()="' . $itemName . '"]//ancestor::td']; $linkXpath = ['xpath' => '//*[text()="' . $itemName . '"]//ancestor::td//a[text()="' . $link . '"]']; $i->moveMouseOver($itemNameCellXpath); $i->waitForElementClickable($linkXpath, 3); $i->click($linkXpath); break; } catch (Exception $exception) { $this->wait(1); continue; } } } public function clickWooTableActionByItemName($itemName, $actionLinkText) { $i = $this; $xpath = ['xpath' => '//tr[.//a[text()="' . $itemName . '"]]//a[text()="' . $actionLinkText . '"]']; $i->waitForElementVisible($xpath); $i->waitForElementClickable($xpath); $i->moveMouseOver($xpath); $i->click($xpath); } public function clickWooTableMoreButtonByItemName($itemName) { $i = $this; $xpath = ['xpath' => '//tr[.//a[text()="' . $itemName . '"]]//div[contains(@class, "mailpoet-listing-more-button")]']; $i->waitForElementClickable($xpath); $i->click($xpath); } public function clickWooTableActionInsideMoreButton(string $itemName, string $moreButtonAction, string $confirmAction = null) { $i = $this; $i->clickWooTableMoreButtonByItemName($itemName); $i->waitForText($itemName); $i->click($moreButtonAction); if ($confirmAction) { $i->click($confirmAction); } } /** * Select a value from select2 input field. * For multiple selection the element is textarea.select2-search__field (default), * for single selection specify the input.select2-search__field element. * * @param string $value * @param string $element */ public function selectOptionInSelect2($value, $element = 'textarea.select2-search__field') { $i = $this; for ($x = 1; $x <= 3; $x++) { try { $i->waitForElement($element); $i->fillField($element, $value); $optionsContainer = $i->grabAttributeFrom($element, 'aria-controls'); Assert::assertIsString($optionsContainer); // Wait until the searched value is in select options. There might be some delay on API $i->waitForText($value, 5, "#$optionsContainer"); $i->pressKey($element, WebDriverKeys::ENTER); $i->seeSelectedInSelect2($value); break; } catch (Exception $exception) { $this->wait(1); continue; } } $i->seeSelectedInSelect2($value); } /** * Check selected value in select2.. * * @param string $value * @param string $element */ public function seeSelectedInSelect2($value, $element = '.select2-container') { $i = $this; $i->waitForElement($element); $i->see($value, $element); } public function selectOptionInReactSelect($value, $selector) { $i = $this; $i->waitForElement($selector); $i->fillField($selector . ' input', $value); $i->pressKey($selector . ' input', WebDriverKeys::RETURN_KEY); } /** * Navigate to the editor for a newsletter. * * @param int|null $id */ public function amEditingNewsletter($id) { $i = $this; if (is_null($id)) { throw new \Exception('No valid id passed'); } $i->amOnPage('/wp-admin/admin.php?page=mailpoet-newsletter-editor&id=' . $id); $i->waitForElement('[data-automation-id="newsletter_title"]'); $i->waitForElementNotVisible('.velocity-animating'); } public function createFormAndSubscribe() { $i = $this; // create form $formName = 'Subscription Acceptance Test Form'; $formFactory = new Form(); $formId = $formFactory->withName($formName)->create()->getId(); $i->havePostInDatabase([ 'post_author' => 1, 'post_type' => 'page', 'post_name' => 'form-test', 'post_title' => 'Form Test', 'post_content' => ' Regular form: [mailpoet_form id="' . $formId . '"] Iframe form: ', 'post_status' => 'publish', ]); $i->amOnPage('/form-test'); $i->waitForElement('[data-automation-id="form_email"]'); $i->fillField('[data-automation-id="form_email"]', 'subscriber@example.com'); $i->click('[data-automation-id="subscribe-submit-button"]'); $messageController = ContainerWrapper::getInstance()->get(FormMessageController::class); $i->waitForText($messageController->getDefaultSuccessMessage(), 30, '.mailpoet_validate_success'); $i->seeNoJSErrors(); } public function waitForListingItemsToLoad() { $i = $this; $i->waitForElementNotVisible('.mailpoet-listing-loading'); } public function waitForEmailSendingOrSent() { $i = $this; $i->waitForElement('.mailpoet-listing-status:not(.mailpoet-listing-status-unknown)', 30); } public function clickLabelWithInput($inputName, $inputValue) { $i = $this; $i->click("//*[name()='label'][.//*[name()='input'][@name='{$inputName}'][@value='{$inputValue}']]"); } public function clickToggleYes($yesNoCSSSelector) { $i = $this; $i->click($yesNoCSSSelector . ' .mailpoet-form-yesno-yes'); } public function clickToggleNo($yesNoCSSSelector) { $i = $this; $i->click($yesNoCSSSelector . ' .mailpoet-form-yesno-no'); } public function assertAttributeContains($selector, $attribute, $contains) { $i = $this; $attributeValue = $i->grabAttributeFrom($selector, $attribute); verify($attributeValue)->stringContainsString($contains); } public function assertCssProperty($cssSelector, $cssProperty, $value) { $i = $this; $attributeValue = $i->executeInSelenium(function (\Facebook\WebDriver\WebDriver $webdriver) use ($cssSelector, $cssProperty){ return $webdriver->findElement(\Facebook\WebDriver\WebDriverBy::cssSelector($cssSelector))->getCSSValue($cssProperty); }); verify($attributeValue)->equals($value); } public function assertAttributeNotContains($selector, $attribute, $notContains) { $i = $this; $attributeValue = $i->grabAttributeFrom($selector, $attribute); verify($attributeValue)->stringNotContainsString($notContains); } public function searchFor($query, $element = '#search_input') { $i = $this; $i->waitForElement($element); $i->waitForElementNotVisible(self::LISTING_LOADING_SELECTOR); $i->clearField($element); $i->fillField($element, $query); $i->pressKey($element, WebDriverKeys::ENTER); $i->waitForElementNotVisible(self::LISTING_LOADING_SELECTOR); } public function createListWithSubscriber() { $segmentFactory = new Segment(); $segmentName = 'List ' . \MailPoet\Util\Security::generateRandomString(); $segment = $segmentFactory->withName($segmentName)->create(); $subscriberFactory = new Subscriber(); $subscriberEmail = \MailPoet\Util\Security::generateRandomString() . '@domain.com'; $subscriberFirstName = 'John'; $subscriberLastName = 'Doe'; $subscriberFactory->withSegments([$segment]) ->withEmail($subscriberEmail) ->withFirstName($subscriberFirstName) ->withLastName($subscriberLastName) ->create(); return $segmentName; } public function switchToNextTab($offset = 1) { // Try switching multiple times. Sometimes we get an exception and maybe the tab is not ready. for ($x = 1; $x <= 3; $x++) { try { $this->_switchToNextTab($offset); break; } catch (Exception $exception) { $this->wait(1); continue; } } // workaround for frozen tabs when opened by clicking on links $this->wait(1); } public function click($link, $context = null) { // retry click in case of "element click intercepted... Other element would receive the click" error $retries = 3; while (true) { try { $retries--; $this->_click($link, $context); break; } catch (WebDriverException $e) { if ($retries > 0 && preg_match('(element click intercepted|element not interactable)', $e->getMessage()) === 1) { $this->wait(0.2); continue; } throw $e; } } } /** * Override waitFor* methods to have a common default timeout */ public function waitForElement($element, $timeout = 10) { return $this->_waitForElement($element, $this->getDefaultTimeout($timeout)); } public function waitForElementChange($element, \Closure $callback, $timeout = 30) { return $this->_waitForElementChange($element, $callback, $this->getDefaultTimeout($timeout)); } public function waitForElementClickable($element, $timeout = 10) { return $this->_waitForElementClickable($element, $this->getDefaultTimeout($timeout)); } public function waitForElementNotVisible($element, $timeout = 10) { return $this->_waitForElementNotVisible($element, $this->getDefaultTimeout($timeout)); } public function waitForElementVisible($element, $timeout = 10) { return $this->_waitForElementVisible($element, $this->getDefaultTimeout($timeout)); } public function waitForJS($script, $timeout = 5) { return $this->_waitForJS($script, $this->getDefaultTimeout($timeout)); } public function waitForText($text, $timeout = 10, $selector = null) { return $this->_waitForText($text, $this->getDefaultTimeout($timeout), $selector); } public function waitForNoticeAndClose($text, $timeout = 10, $selector = null) { $this->_waitForText($text, $this->getDefaultTimeout($timeout), $selector); $this->waitForElementVisible('.notice-dismiss', 1); $this->click('.notice-dismiss'); } public function closeNoticeIfVisible() { // Dismiss notice if it is visible. This is needed especially for the tests with the lowest supported PHP version // because we display a warning about the minimum required PHP version. $noticeIsVisible = $this->executeJS('return document.getElementsByClassName("notice-dismiss")'); if ($noticeIsVisible) { $this->click('.notice-dismiss'); $this->waitForElementNotVisible('.notice-dismiss', 3); } } public function scrollToTop() { return $this->scrollTo('#wpcontent'); } private function getDefaultTimeout($timeout) { return (int)getenv('WAIT_TIMEOUT') ?: $timeout; } public function activateWooCommerce() { $i = $this; $i->cli(['plugin', 'activate', self::WOO_COMMERCE_PLUGIN]); } public function deactivateWooCommerce() { $i = $this; $i->cli(['plugin', 'deactivate', self::WOO_COMMERCE_PLUGIN]); } public function activateWooCommerceMemberships() { $i = $this; $i->cli(['plugin', 'activate', self::WOO_COMMERCE_MEMBERSHIPS_PLUGIN]); } public function deactivateWooCommerceMemberships() { $i = $this; $i->cli(['plugin', 'deactivate', self::WOO_COMMERCE_MEMBERSHIPS_PLUGIN]); } public function activateWooCommerceSubscriptions() { $i = $this; $i->cli(['plugin', 'activate', self::WOO_COMMERCE_SUBSCRIPTIONS_PLUGIN]); } public function deactivateWooCommerceSubscriptions() { $i = $this; $i->cli(['plugin', 'deactivate', self::WOO_COMMERCE_SUBSCRIPTIONS_PLUGIN]); } public function activateAutomateWoo() { $i = $this; $i->cli(['plugin', 'activate', self::AUTOMATE_WOO_PLUGIN]); } public function deactivateAutomateWoo() { $i = $this; $i->cli(['plugin', 'deactivate', self::AUTOMATE_WOO_PLUGIN]); } public function checkPluginIsActive(string $plugin): bool { $i = $this; $activePlugins = $i->grabOptionFromDatabase('active_plugins', true); Assert::assertIsArray($activePlugins); return in_array($plugin, $activePlugins); } public function getWooCommerceVersion(): string { $i = $this; return $i->cliToString(['plugin', 'get', self::WOO_COMMERCE_PLUGIN, '--field=version']); } public function getWooCommerceBlocksVersion(): string { $i = $this; return $i->cliToString(['plugin', 'get', self::WOO_COMMERCE_BLOCKS_PLUGIN, '--field=version']); } public function getWordPressVersion(): string { $i = $this; return $i->cliToString(['core', 'version']); } public function orderProductWithoutRegistration(array $product, $userEmail, $doSubscribe = true) { $this->orderProduct($product, $userEmail, false, $doSubscribe); } public function orderProductWithRegistration(array $product, $userEmail, $doSubscribe = true) { $this->orderProduct($product, $userEmail, true, $doSubscribe); } /** * Order a product and create an account within the order process * We are also checking the WooCommerce version and if it has new or old checkout experience */ public function orderProduct(array $product, $userEmail, $doRegister = true, $doSubscribe = true) { $i = $this; $i->amOnPage('checkout/?add-to-cart=' . $product['id']); $i->fillCustomerInfo($userEmail); $wooCommerceVersion = $i->getWooCommerceVersion(); if ($doSubscribe) { if (version_compare($wooCommerceVersion, '8.3.0', '>=')) { $settings = (ContainerWrapper::getInstance())->get(SettingsController::class); $i->click(Locator::contains('label', $settings->get('woocommerce.optin_on_checkout.message'))); } else { $isCheckboxVisible = $i->executeJS('return document.getElementById("mailpoet_woocommerce_checkout_optin")'); if ($isCheckboxVisible) { $i->checkOption('#mailpoet_woocommerce_checkout_optin'); } } } else { if (version_compare($wooCommerceVersion, '8.3.0', '<')) { $isCheckboxVisible = $i->executeJS('return document.getElementById("mailpoet_woocommerce_checkout_optin")'); if ($isCheckboxVisible) { $i->uncheckOption('#mailpoet_woocommerce_checkout_optin'); } } } if ($doRegister) { $i->optInForRegistration(); } $i->selectPaymentMethod(); $i->placeOrder(); if ($doRegister) { $i->logOut(); } // ensure action scheduler jobs are done $i->triggerMailPoetActionScheduler(); } /** * WooCommerce ordering process methods, should be used sequentially */ /** * Add a product to cart */ public function addProductToCart(array $product) { $i = $this; $i->amOnPage('product/' . $product['slug']); $i->click('Add to cart'); $i->waitForText("“{$product['name']}” has been added to your cart."); } /** * Go to the shortcode checkout page */ public function goToShortcodeCheckout() { $i = $this; $i->amOnPage('shortcode-checkout'); } /** * Go to the block checkout page */ public function goToBlockCheckout() { $i = $this; $i->amOnPage('checkout'); } /** * Fill the customer info * We are also checking the WooCommerce version and if it has new or old checkout experience */ public function fillCustomerInfo($userEmail) { $i = $this; $wooCommerceVersion = $i->getWooCommerceVersion(); if (version_compare($wooCommerceVersion, '8.3.0', '>=')) { $i->fillField('#billing-first_name', 'John'); $i->fillField('#billing-last_name', 'Doe'); $i->fillField('#billing-address_1', 'Address 1'); $i->fillField('#billing-city', 'Paris'); $i->fillField('#email', $userEmail); $i->fillField('#billing-postcode', '75000'); $i->fillField('#billing-phone', '0555666777'); } else { $i->fillField('billing_first_name', 'John'); $i->fillField('billing_last_name', 'Doe'); $i->fillField('billing_address_1', 'Address 1'); $i->fillField('billing_city', 'Paris'); $i->fillField('billing_email', $userEmail); $i->fillField('billing_postcode', '75000'); $i->fillField('billing_phone', '123456'); } } /** * Check the option for creating an account * We are also checking the WooCommerce version and if it has new or old checkout experience */ public function optInForRegistration() { $i = $this; $wooCommerceVersion = $i->getWooCommerceVersion(); if (version_compare($wooCommerceVersion, '8.3.0', '>=')) { $isCheckboxVisible = $i->executeJS('return document.getElementsByClassName("wc-block-checkout__create-account")'); if ($isCheckboxVisible) { $i->click(Locator::contains('label', 'Create an account')); } } else { $isCheckboxVisible = $i->executeJS('return document.getElementById("createaccount")'); if ($isCheckboxVisible) { $i->checkOption('#createaccount'); } } } /** * Check the option for subscribing to the WC list */ public function optInForSubscription() { $settings = (ContainerWrapper::getInstance())->get(SettingsController::class); $i = $this; $isCheckboxVisible = $i->executeJS('return document.getElementById("checkbox-control-0")'); if ($isCheckboxVisible) { $i->click(Locator::contains('label', $settings->get('woocommerce.optin_on_checkout.message'))); } } /** * Uncheck the option for subscribing to the WC list */ public function optOutOfSubscription() { $settings = (ContainerWrapper::getInstance())->get(SettingsController::class); $i = $this; $isCheckboxVisible = $i->executeJS('return document.getElementById("checkbox-control-0")'); if ($isCheckboxVisible) { $i->click(Locator::contains('label', $settings->get('woocommerce.optin_on_checkout.message'))); } } /** * Select a payment method (cheque, cod, ppec_paypal) * We are also checking the WooCommerce version and if it has new or old checkout experience */ public function selectPaymentMethod($method = 'cod') { $i = $this; $wooCommerceVersion = $i->getWooCommerceVersion(); if (version_compare($wooCommerceVersion, '8.3.0', '>=')) { // We need to scroll with some negative offset so that the input is not hidden above the top page fold $approximatePaymentMethodInputHeight = 40; $i->waitForElementNotVisible('.blockOverlay', 30); // wait for payment method loading overlay to disappear $i->scrollTo('#radio-control-wc-payment-method-options-' . $method, 0, -$approximatePaymentMethodInputHeight); $i->click('label[for="radio-control-wc-payment-method-options-' . $method . '"]'); $i->wait(0.5); // Wait for animation after selecting the method. } else { $approximatePaymentMethodInputHeight = 40; $i->waitForElementNotVisible('.blockOverlay', 30); // wait for payment method loading overlay to disappear $i->scrollTo('#payment_method_' . $method, 0, -$approximatePaymentMethodInputHeight); $i->click('label[for="payment_method_' . $method . '"]'); $i->wait(0.5); // Wait for animation after selecting the method. } } /** * Place the order * We are also checking the WooCommerce version and if it has new or old checkout experience */ public function placeOrder() { $i = $this; $wooCommerceVersion = $i->getWooCommerceVersion(); if (version_compare($wooCommerceVersion, '8.3.0', '>=')) { // Add a note to order just to avoid flakiness due to race conditions $i->click(Locator::contains('label', 'Add a note to your order')); $i->fillField('.wc-block-components-textarea', 'This is a note'); $i->waitForText('Place Order'); $i->waitForElementClickable(Locator::contains('button', 'Place Order')); $i->click(Locator::contains('button', 'Place Order')); $i->waitForText('Thank you. Your order has been received.'); } else { $i->waitForText('Place order'); $i->click('Place order'); $i->waitForText('Thank you. Your order has been received.'); } } /** * Register a customer on my-account page * @param string $email * @param bool $optIn Whether to check optin checkbox or not * @throws UnrecognizedExceptionException */ public function registerCustomerOnMyAccountPage(string $email, $optIn = false) { $i = $this; $i->amOnPage('my-account'); $i->fillField('Email address', $email); if ($optIn) { $i->checkOption('mailpoet[subscribe_on_register]'); } $i->click('Register', '.woocommerce-form-register'); $i->waitForText('From your account dashboard you can view your recent orders'); } // Enforce WP-CLI to be called with array because: // - It's recommended now (https://github.com/lucatume/wp-browser/commit/6dbf93709194c630191c0c7de527b577105be743). // - It's default in Symfony\Process now. // - String variant is still buggy even after a fix (https://github.com/lucatume/wp-browser/commit/b078ef37917b4f0668d064ea950e4b41f1773cb6). public function cli(array $userCommand) { return $this->_cli($userCommand); } public function cliToArray(array $userCommand) { return $this->_cliToArray($userCommand); } public function cliToString(array $userCommand) { return $this->_cliToString($userCommand); } /** * Creates post and returns its URL */ public function createPost(string $title, string $body): string { $post = $this->cliToString(['post', 'create', '--format=json', '--porcelain', '--post_status=publish', '--post_type=post', '--post_title="' . $title . '"', '--post_content="' . $body . '"']); $postData = $this->cliToString(['post', 'get', $post, '--format=json']); $postData = json_decode($postData, true); Assert::assertIsArray($postData); Assert::assertIsString($postData['guid']); return $postData['guid']; } public function addFromBlockInEditor($name, $context = null) { $i = $this; $appender = '[data-automation-id="form_inserter_open"]'; if ($context) { $appender = "$context $appender"; } $i->click($appender);// CLICK the button that adds new blocks $i->fillField('.block-editor-inserter__search [placeholder="Search"]', $name); $i->waitForText($name, 5, '.block-editor-block-types-list__item-title'); $i->click($name, '.block-editor-block-types-list__list-item'); $i->click($appender);// close the inserter } public function saveFormInEditor() { $i = $this; $i->click('[data-automation-id="form_save_button"]'); $i->waitForText('Form saved', 10, '.automation-dismissible-notices'); $i->seeNoJSErrors(); } /** * Select the panel color in the form editor * Selectable colors are: Black [1], Gray [2], White [3], Pink [4], Red [5], Orange [6], * Yellow [7], Turquoise [8], Green [9], Cyan [10], Blue [11] and Purple [12]. * Please select colors by providing [number] as string. * @param string $colorOrder */ public function selectPanelColor($colorOrder) { $i = $this; $i->click('(//div[@class="components-circular-option-picker__option-wrapper"])' . $colorOrder); } /** * Checks that email was received by looking for a subject in inbox. * In case it was not found reloads the inbox and check once more. * Emails are sent via cron and might not be sent immediately. * @param string $subject */ public function checkEmailWasReceived($subject) { $i = $this; $i->amOnMailboxAppPage(); try { $i->waitForText($subject, 30); } catch (\Exception $e) { $i->amOnMailboxAppPage(); $i->waitForText($subject, 60); } } /** * Checks that email was not received by looking for a subject in inbox. * @param string $subject */ public function checkEmailWasNotReceived($subject) { $i = $this; $i->triggerMailPoetActionScheduler(); $i->amOnMailboxAppPage(); $i->dontSee($subject); // click refresh button to seek for new emails once again $i->click('.glyphicon-refresh'); $i->dontSee($subject); } /** * Checks if the subscriber has correct global status * and if some lists are passed also validates that they are subscribed in those lists * @param string $email * @param string $status * @param string[]|null $listsSubscribed Array of lists in that subscriber should be subscribed * @param string[]|null $listsNotSubscribed Array of lists in that subscriber shouldn't be subscribed */ public function checkSubscriberStatusAndLists(string $email, string $status, $listsSubscribed = null, $listsNotSubscribed = null) { $i = $this; $i->amOnMailpoetPage('Subscribers'); $i->searchFor($email); $i->waitForListingItemsToLoad(); $i->waitForText($email); $i->see(ucfirst($status), 'td[data-colname="Status"]'); if (is_array($listsSubscribed)) { foreach ($listsSubscribed as $list) { $i->see($list, 'td[data-colname="Lists"]'); } } if (is_array($listsNotSubscribed)) { foreach ($listsNotSubscribed as $list) { $i->dontSee($list, 'td[data-colname="Lists"]'); } } } /** * Checks if any confirmation email is in mailbox */ public function seeConfirmationEmailWasReceived() { $this->checkEmailWasReceived('Confirm your subscription to'); } /** * Checks if there are no confirmation emails in mailbox */ public function seeConfirmationEmailWasNotReceived() { $i = $this; $i->amOnMailboxAppPage(); $i->dontSee('Confirm your subscription to'); } /** * Makes sure that there is a newsletter template of given order on given template tab * @return string Template element selector */ public function checkTemplateIsPresent(int $templateIndex, string $templateCategory = 'standard'): string { $templateTab = "[data-automation-id=\"templates-$templateCategory\"]"; $i = $this; $i->waitForElement($templateTab); $i->click($templateTab); $template = "[data-automation-id=\"select_template_$templateIndex\"]"; $i->waitForElement($template); return $template; } public function clearFormField(string $selector) { $i = $this; $i->click($selector); // Focus in the field $value = $i->grabAttributeFrom($selector, 'value'); Assert::assertIsString($value); for ($j = 0; $j < mb_strlen($value); $j++) { $i->pressKey($selector, WebDriverKeys::BACKSPACE);// delete the field } } public function canTestWithPlugin(string $pluginSlug): bool { $i = $this; try { $result = $i->cli(['plugin', 'is-installed', $pluginSlug]); } catch (\Exception $e) { return false; } return (int)$result === 0; } /** * Some tests rely on background job processing. * The processing runs in 1 minute interval (default Action Scheduler interval) * This method triggers the processing immediately so that tests don't have to wait. */ public function triggerMailPoetActionScheduler(): void { $i = $this; // Reschedule MailPoet's daemon trigger action to run immediately $i->importSql([ "UPDATE mp_actionscheduler_actions SET scheduled_date_gmt = SUBTIME(now(), '01:00:00'), scheduled_date_local = SUBTIME(now(), '01:00:00') WHERE hook = 'mailpoet/cron/daemon-trigger' AND status = 'pending';", ]); $i->cli(['action-scheduler', 'run', '--force']); } public function triggerAutomationActionScheduler(): void { $i = $this; // Reschedule automation trigger action to run immediately $i->importSql([ "UPDATE mp_actionscheduler_actions SET scheduled_date_gmt = SUBTIME(now(), '01:00:00'), scheduled_date_local = SUBTIME(now(), '01:00:00') WHERE hook = 'mailpoet/automation/step' AND status = 'pending';", ]); $i->cli(['action-scheduler', 'run', '--force']); } public function isWooCustomOrdersTableEnabled(): bool { return (bool)getenv('ENABLE_COT'); } public function changeGroupInListingFilter(string $name): void { $i = $this; for ($x = 1; $x <= 3; $x++) { try { $i->waitForElementClickable('[data-automation-id="filters_' . $name . '"]'); $i->click('[data-automation-id="filters_' . $name . '"]'); $i->seeInCurrentURL(urlencode('group[' . $name . ']')); break; } catch (Exception $exception) { $this->wait(0.5); continue; } } $i->seeInCurrentURL(urlencode('group[' . $name . ']')); } public function checkWooTableCheckboxForItemName(string $itemName): void { $i = $this; $xpath = ['xpath' => '//tr[.//a[text()="' . $itemName . '"]]//input[@type="checkbox"]']; $i->click($xpath); } public function changeWooTableTab(string $name): void { $i = $this; $i->click('button.mailpoet-tab-' . $name); } public function clearTransientCache(): void { $cache = ContainerWrapper::getInstance()->get(TransientCache::class); $cache->invalidateAllItems(); } public function selectSegmentTemplate($templateName) { $i = $this; $segmentName = Locator::contains('.mailpoet-templates-card-header-title', $templateName); $i->waitForElement($segmentName); $i->click($segmentName); } }