public function testCreateTree()
 {
     $shop = self::getShop();
     $cm = $shop->getCategoryManager();
     $cats = ['Selenium Tree Root', 'Selenium Child 1', 'Selenium Child 2'];
     $tree = [2];
     $tree[] = $cm->createCategory(['name' => $cats[0], 'parent' => end($tree)]);
     $tree[] = $cm->createCategory(['name' => $cats[1], 'parent' => end($tree)]);
     $tree[] = $cm->createCategory(['name' => $cats[2], 'parent' => end($tree)]);
     // Add our category to blocktopmenu
     $browser = $shop->getBackOfficeNavigator()->visitModuleConfigurationPage('blocktopmenu');
     $browser->click('#availableItems option[value="CAT' . $tree[1] . '"]')->click('#addItem')->click('#module_form_submit_btn');
     $shop->expectStandardSuccessMessage();
     $shop->getFrontOfficeNavigator()->visitHome();
     $found = [];
     $spinner = new Spinner();
     $spinner->assertBecomesTrue(function () use($browser, $cats, &$found) {
         $browser->hover('#block_top_menu a[title="' . $cats[0] . '"]');
         sleep(1);
         $links = $browser->find('#block_top_menu ul.submenu-container a', ['unique' => false]);
         foreach ($links as $n => $link) {
             $text = strtolower(trim($link->getText()));
             if ($text !== '') {
                 $found[$text] = true;
             }
         }
         return count($found) > 0;
     });
     foreach ($cats as $n => $cat) {
         if ($n > 0 && !isset($found[strtolower(trim($cat))])) {
             throw new \PrestaShop\PSTAF\Exception\FailedTestException("Did not find this cat in the topmenu: {$cat}.");
         }
     }
 }
 public function testSubdomainsCanBeBound()
 {
     $storeDetailsPage = $this->homePage->visit()->gotoMyStores()->gotoDetails();
     $domain = md5(microtime()) . $this->getSecrets()['subdomain'];
     $storeDetailsPage->bindDomain($domain);
     $detailsPage = $this->homePage->visit()->gotoMyStores()->gotoDetails();
     $spinner = new Spinner('Domain `' . $domain . '` wasn\'t bound in 30 minutes.', 1800, 1000);
     $spinner->assertBecomesTrue(function () use($detailsPage, $domain) {
         $this->getBrowser()->reload();
         return $detailsPage->isDomainActive($domain);
     });
 }
 public function waitFor200($url)
 {
     $spinner = new Spinner('Did not find final FO URL in 1 hour.', 3600, 1000);
     $spinner->assertBecomesTrue(function () use($url) {
         $ch = curl_init($url);
         curl_setopt($ch, CURLOPT_NOBODY, true);
         curl_exec($ch);
         $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
         curl_close($ch);
         return $status == 200;
     });
 }
 public function ensureAnEmailIsSentTo($address, $timeout_in_seconds = 300, array $options = array())
 {
     $spinner = new Spinner("Did not get required email on {$address} after {$timeout_in_seconds}s.", $timeout_in_seconds, 10000);
     $spinner->assertBecomesTrue(function () use($address, $options) {
         $emails = $this->readEmails($address);
         if (isset($options['body']['contains'])) {
             foreach ($emails as $email) {
                 if (false !== strpos($email['body'], $options['body']['contains'])) {
                     return true;
                 }
             }
             return false;
         } else {
             return count($emails) > 0;
         }
     });
     return $this;
 }
 private function _jqcSelect($selector, $value)
 {
     $spinner = new Spinner('Could not select value (JQuery Chosen Select)', $this->defaultTimeout, $this->defaultInterval);
     return $spinner->assertNoException(function () use($selector, $value) {
         return $this->tryJqcSelect($selector, $value);
     });
 }
 public function emailsAreSent()
 {
     $this->browser->clearCookies();
     $this->shop->getBackOfficeNavigator()->login();
     $emails = $this->shop->getPageObject('AdminEmails')->visit();
     $emailTestAddress = $this->getEmailTestAddress();
     $spinner = new Spinner('Could not send an email (after 10 minutes).', 600, 5000);
     $spinner->assertNoException(function () use($emails, $emailTestAddress) {
         $emails->sendTestEmailTo($emailTestAddress);
     });
     $this->getEmailReader()->ensureAnEmailIsSentTo($emailTestAddress);
 }
 public static function spawnSelenium($headless = false)
 {
     $display = null;
     if ($headless) {
         for ($displayNumber = 10; $displayNumber < 50; ++$displayNumber) {
             try {
                 $xprocess = new Process('Xvfb', [':' . $displayNumber], ['-ac' => ''], ['upid' => true]);
                 $xprocess->run();
                 sleep(1);
             } catch (Exception $e) {
                 // never mind, try next display...
             }
             if ($xprocess->running()) {
                 self::$processesToKill[] = $xprocess;
                 $display = ':' . $displayNumber;
                 break;
             }
         }
     }
     list($process, $port) = self::buildStartProcess();
     self::$processesToKill[] = $process;
     if ($display) {
         $process->setEnv('DISPLAY', $display);
     }
     $process->run(null, 'selenium.log', 'selenium.log');
     if ($process->running()) {
         self::$host = 'http://127.0.0.1:' . $port . '/wd/hub';
         $spinner = new Spinner('Could not automatically start selenium.', 20, 1000);
         $spinner->assertBecomesTrue(function () {
             $ch = curl_init(self::$host . '/status');
             curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
             $status = json_decode(curl_exec($ch), true);
             curl_close($ch);
             return isset($status['status']) && $status['status'] === 0;
         });
     }
     register_shutdown_function(function () {
         self::unSpawnSelenium();
     });
 }
    /**
     * Create a product
     * $options is an array with the the following keys:
     * - name
     * - price: price before tax
     * - quantity: quantity (only regular stock, not advanced one)
     * - tax_rules_group: id of the tax group to use for this product
     *
     * Returns an array with keys:
     * - id: the id of the product
     * - fo_url: the URL to access this product in FO
     */
    public function createProduct($options)
    {
        $browser = $this->getShop()->getBackOfficeNavigator()->visit('AdminProducts', 'new');
        $saveSpinner = new Spinner('Could not save product.', 60, 2000);
        // Try this in a loop, because the javascript that populates link rewrite is
        // very unstable.
        $saveSpinner->assertNoException(function () use($browser, $options) {
            $browser->click('#link-Informations')->sleep(1)->fillIn($this->i18nFieldName('#name'), $options['name'])->sleep(1)->click('#link-Prices')->waitFor('#priceTE')->fillIn('#priceTE', $options['price'])->select('#id_tax_rules_group', empty($options['tax_rules_group']) ? 0 : $options['tax_rules_group']);
            $this->saveProduct();
        });
        if (isset($options['specific_price'])) {
            $browser->click('#link-Prices')->waitFor('#priceTE');
            $from_quantity = 1;
            $m = [];
            if (preg_match('/^\\s*(\\d+(?:\\.\\d+)?)\\s*%\\s*(?:starting\\s+at\\s+unit\\s+(\\d+))?$/', $options['specific_price'], $m)) {
                $percentage = $m[1];
                $from_quantity = $m[2];
            } else {
                throw new \Exception("Invalid specific price specified: {$options['specific_price']}.");
            }
            $browser->click('#show_specific_price')->select('#sp_reduction_type', 'percentage')->fillIn('#sp_reduction', $percentage);
            if ($from_quantity > 1) {
                $browser->fillIn('#sp_from_quantity', $from_quantity);
            }
            $this->saveProduct();
        }
        if (!empty($options['quantity'])) {
            $browser->click('#link-Quantities')->waitFor('#qty_0');
            /**
             * Ok, so this next part is tricky!
             *
             * We need to detect the successful change of the quantity field.
             * So, we trigger the change by injecting javascript, and watch the DOM
             * to detect the success notification (div.growl.growl-notice).
             *
             * We need to watch the DOM before triggering the event, because to make
             * things easier, the notification is transient.
             *
             * This is a bit suboptimal because it fails to emulate exactly the user behaviour,
             * but it should be close enough. If anybody has a better idea, please PR!
             *
             */
            $qset = <<<'EOS'
    var quantity = arguments[0];
    var done = arguments[1]; // Selenium wraps us inside a function, and we need to call done when done.
    var observer = new MutationObserver(function () {
        if ($('#growls .growl.growl-notice').length > 0) {
            done();
            observer.disconnect();
        }
    });
    observer.observe(document.documentElement, {childList: true, subtree: true});
    $("#qty_0 input").val(quantity);
    $("#qty_0 input").trigger("change");
EOS;
            try {
                $browser->setScriptTimeout(5);
                $spinner = new Spinner(null, 20, 5000);
                $spinner->assertNoException(function () use($browser, $qset, $options) {
                    $browser->executeAsyncScript($qset, [$options['quantity']]);
                });
            } catch (\ScriptTimeoutException $e) {
                throw new \PrestaShop\PSTAF\Exception\ProductCreationIncorrectException('Could not set quantity.');
            }
            $this->saveProduct();
            $browser->click('#link-Quantities')->waitFor('#qty_0');
            $actualQuantity = (int) $this->i18nParse($browser->getValue('#qty_0 input'), 'float');
            $expectedQuantity = (int) $options['quantity'];
            if ($expectedQuantity !== $actualQuantity) {
                throw new \PrestaShop\PSTAF\Exception\ProductCreationIncorrectException('quantity', $expectedQuantity, $actualQuantity);
            }
        }
        $dimensions = ['width', 'height', 'depth', 'weight'];
        $onShippingTab = false;
        foreach ($dimensions as $dimension) {
            if (!empty($options[$dimension])) {
                if (!$onShippingTab) {
                    $browser->click('#link-Shipping')->waitFor("#{$dimension}");
                    $onShippingTab = true;
                }
                $browser->fillIn("#{$dimension}", $options[$dimension]);
            }
        }
        if ($onShippingTab) {
            $this->saveProduct();
        }
        $browser->click('#link-Prices')->waitFor('#priceTE');
        $expected_price = (double) $options['price'];
        $actual_price = $this->i18nParse($browser->getValue('#priceTE'));
        if ($actual_price !== $expected_price) {
            throw new \PrestaShop\PSTAF\Exception\ProductCreationIncorrectException('price', $expected_price, $actual_price);
        }
        $id_product = (int) $browser->getURLParameter('id_product');
        if ($id_product <= 0) {
            throw new \PrestaShop\PSTAF\Exception\ProductCreationIncorrectException();
        }
        return ['id' => $id_product, 'fo_url' => $browser->getAttribute('#page-header-desc-product-preview', 'href')];
    }
 /**
  * @depends testSubdomainsFromDeletedShopRemainAvailableToCustomer
  */
 public function testDomainsFromDeletedShopCanBeReAssigned($domainsPage)
 {
     $domainsPage->assignDomainToShop(self::getValue('sd1'), self::getValue('uidB'));
     $domainsPage->assignDomainToShop(self::getValue('sd3'), self::getValue('uidB'));
     $spinner = new Spinner('Domains from deleted shop could not be re-assigned to surviving shop.', 1800, 1000);
     $spinner->assertBecomesTrue(function () use($domainsPage) {
         $this->getBrowser()->reload();
         return $domainsPage->isGreen(self::getValue('sd1')) && $domainsPage->isGreen(self::getValue('sd3'));
     });
 }