├── .gitignore ├── CHANGELOG.md ├── src ├── Page │ ├── UnexpectedPageException.php │ ├── SymfonyPageInterface.php │ ├── PageInterface.php │ ├── SymfonyPage.php │ └── Page.php └── Element │ └── Element.php ├── README.md └── composer.json /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | 3 | /composer.lock 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## CHANGELOG 2 | 3 | ### v0.3.2 (2020-11-05) 4 | 5 | - [#16](https://github.com/FriendsOfBehat/PageObjectExtension/issues/16) allow PHP 8 ([@dunglas](https://github.com/dunglas)) -------------------------------------------------------------------------------- /src/Page/UnexpectedPageException.php: -------------------------------------------------------------------------------- 1 | 'en_US']; 17 | 18 | /** 19 | * @param array|\ArrayAccess $minkParameters 20 | */ 21 | public function __construct(Session $session, $minkParameters, RouterInterface $router) 22 | { 23 | if (!is_array($minkParameters) && !$minkParameters instanceof \ArrayAccess) { 24 | throw new \InvalidArgumentException(sprintf( 25 | '"$parameters" passed to "%s" has to be an array or implement "%s".', 26 | self::class, 27 | \ArrayAccess::class 28 | )); 29 | } 30 | 31 | parent::__construct($session, $minkParameters); 32 | 33 | $this->router = $router; 34 | } 35 | 36 | abstract public function getRouteName(): string; 37 | 38 | /** 39 | * @throws UnexpectedPageException 40 | */ 41 | public function verifyRoute(array $requiredUrlParameters = []): void 42 | { 43 | $url = $this->getDriver()->getCurrentUrl(); 44 | $path = parse_url($url)['path']; 45 | 46 | $path = preg_replace('#^/app(_dev|_test|_test_cached)?\.php/#', '/', $path); 47 | $matchedRoute = $this->router->match($path); 48 | 49 | $this->verifyRouteName($matchedRoute, $url); 50 | $this->verifyRouteParameters($requiredUrlParameters, $matchedRoute); 51 | } 52 | 53 | final protected function makePathAbsolute(string $path): string 54 | { 55 | $baseUrl = rtrim($this->getParameter('base_url'), '/') . '/'; 56 | 57 | return 0 !== strpos($path, 'http') ? $baseUrl . ltrim($path, '/') : $path; 58 | } 59 | 60 | protected function getUrl(array $urlParameters = []): string 61 | { 62 | $path = $this->router->generate($this->getRouteName(), $urlParameters + static::$additionalParameters); 63 | 64 | $replace = []; 65 | foreach (static::$additionalParameters as $key => $value) { 66 | $replace[sprintf('&%s=%s', $key, $value)] = ''; 67 | $replace[sprintf('?%s=%s&', $key, $value)] = '?'; 68 | $replace[sprintf('?%s=%s', $key, $value)] = ''; 69 | } 70 | 71 | $path = str_replace(array_keys($replace), array_values($replace), $path); 72 | 73 | return $this->makePathAbsolute($path); 74 | } 75 | 76 | protected function verifyUrl(array $urlParameters = []): void 77 | { 78 | $url = $this->getDriver()->getCurrentUrl(); 79 | $path = parse_url($url)['path']; 80 | 81 | $path = preg_replace('#^/app(_dev|_test|_test_cached)?\.php/#', '/', $path); 82 | $matchedRoute = $this->router->match($path); 83 | 84 | if (isset($matchedRoute['_locale'])) { 85 | $urlParameters += ['_locale' => $matchedRoute['_locale']]; 86 | } 87 | 88 | parent::verifyUrl($urlParameters); 89 | } 90 | 91 | /** 92 | * @throws UnexpectedPageException 93 | */ 94 | private function verifyRouteName(array $matchedRoute, string $url): void 95 | { 96 | if ($matchedRoute['_route'] !== $this->getRouteName()) { 97 | throw new UnexpectedPageException( 98 | sprintf( 99 | "Matched route '%s' does not match the expected route '%s' for URL '%s'", 100 | $matchedRoute['_route'], 101 | $this->getRouteName(), 102 | $url 103 | ) 104 | ); 105 | } 106 | } 107 | 108 | /** 109 | * @throws UnexpectedPageException 110 | */ 111 | private function verifyRouteParameters(array $requiredUrlParameters, array $matchedRoute): void 112 | { 113 | foreach ($requiredUrlParameters as $key => $value) { 114 | if (!isset($matchedRoute[$key]) || $matchedRoute[$key] !== $value) { 115 | throw new UnexpectedPageException( 116 | sprintf( 117 | "Matched route does not match the expected parameter '%s'='%s' (%s found)", 118 | $key, 119 | $value, 120 | $matchedRoute[$key] ?? 'null' 121 | ) 122 | ); 123 | } 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/Element/Element.php: -------------------------------------------------------------------------------- 1 | session = $session; 39 | $this->parameters = $minkParameters; 40 | } 41 | 42 | protected function getParameter(string $name) 43 | { 44 | return $this->parameters[$name] ?? null; 45 | } 46 | 47 | protected function getDefinedElements(): array 48 | { 49 | return []; 50 | } 51 | 52 | /** 53 | * @throws ElementNotFoundException 54 | */ 55 | protected function getElement(string $name, array $parameters = []): NodeElement 56 | { 57 | $element = $this->createElement($name, $parameters); 58 | 59 | if (!$this->getDocument()->has('xpath', $element->getXpath())) { 60 | throw new ElementNotFoundException( 61 | $this->getSession(), 62 | sprintf('Element named "%s" with parameters %s', $name, implode(', ', $parameters)), 63 | 'xpath', 64 | $element->getXpath() 65 | ); 66 | } 67 | 68 | return $element; 69 | } 70 | 71 | protected function hasElement(string $name, array $parameters = []): bool 72 | { 73 | return $this->getDocument()->has('xpath', $this->createElement($name, $parameters)->getXpath()); 74 | } 75 | 76 | protected function getSession(): Session 77 | { 78 | return $this->session; 79 | } 80 | 81 | protected function getDriver(): DriverInterface 82 | { 83 | return $this->session->getDriver(); 84 | } 85 | 86 | protected function getDocument(): DocumentElement 87 | { 88 | if (null === $this->document) { 89 | $this->document = new DocumentElement($this->session); 90 | } 91 | 92 | return $this->document; 93 | } 94 | 95 | private function createElement(string $name, array $parameters = []): NodeElement 96 | { 97 | $definedElements = $this->getDefinedElements(); 98 | 99 | if (!isset($definedElements[$name])) { 100 | throw new \InvalidArgumentException(sprintf( 101 | 'Could not find a defined element with name "%s". The defined ones are: %s.', 102 | $name, 103 | implode(', ', array_keys($definedElements)) 104 | )); 105 | } 106 | 107 | $elementSelector = $this->resolveParameters($name, $parameters, $definedElements); 108 | 109 | return new NodeElement( 110 | $this->getSelectorAsXpath($elementSelector, $this->session->getSelectorsHandler()), 111 | $this->session 112 | ); 113 | } 114 | 115 | private function getSelectorAsXpath($selector, SelectorsHandler $selectorsHandler): string 116 | { 117 | $selectorType = is_array($selector) ? key($selector) : 'css'; 118 | $locator = is_array($selector) ? $selector[$selectorType] : $selector; 119 | 120 | return $selectorsHandler->selectorToXpath($selectorType, $locator); 121 | } 122 | 123 | private function resolveParameters(string $name, array $parameters, array $definedElements): string 124 | { 125 | if (!is_array($definedElements[$name])) { 126 | return strtr($definedElements[$name], $parameters); 127 | } 128 | 129 | array_map( 130 | function ($definedElement) use ($parameters): string { 131 | return strtr($definedElement, $parameters); 132 | }, $definedElements[$name] 133 | ); 134 | 135 | return $definedElements[$name]; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Page/Page.php: -------------------------------------------------------------------------------- 1 | session = $session; 40 | $this->parameters = $minkParameters; 41 | } 42 | 43 | public function open(array $urlParameters = []): void 44 | { 45 | $this->tryToOpen($urlParameters); 46 | $this->verify($urlParameters); 47 | } 48 | 49 | public function tryToOpen(array $urlParameters = []): void 50 | { 51 | $this->getSession()->visit($this->getUrl($urlParameters)); 52 | } 53 | 54 | public function verify(array $urlParameters = []): void 55 | { 56 | $this->verifyStatusCode(); 57 | $this->verifyUrl($urlParameters); 58 | } 59 | 60 | public function isOpen(array $urlParameters = []): bool 61 | { 62 | try { 63 | $this->verify($urlParameters); 64 | } catch (\Exception $exception) { 65 | return false; 66 | } 67 | 68 | return true; 69 | } 70 | 71 | abstract protected function getUrl(array $urlParameters = []): string; 72 | 73 | /** 74 | * @throws UnexpectedPageException 75 | */ 76 | protected function verifyStatusCode(): void 77 | { 78 | try { 79 | $statusCode = $this->getSession()->getStatusCode(); 80 | } catch (DriverException $exception) { 81 | return; // Ignore drivers which cannot check the response status code 82 | } 83 | 84 | if ($statusCode >= 200 && $statusCode <= 299) { 85 | return; 86 | } 87 | 88 | $currentUrl = $this->getSession()->getCurrentUrl(); 89 | $message = sprintf('Could not open the page: "%s". Received an error status code: %s', $currentUrl, $statusCode); 90 | 91 | throw new UnexpectedPageException($message); 92 | } 93 | 94 | /** 95 | * Overload to verify if the current url matches the expected one. Throw an exception otherwise. 96 | * 97 | * @throws UnexpectedPageException 98 | */ 99 | protected function verifyUrl(array $urlParameters = []): void 100 | { 101 | if ($this->getSession()->getCurrentUrl() !== $this->getUrl($urlParameters)) { 102 | throw new UnexpectedPageException(sprintf('Expected to be on "%s" but found "%s" instead', $this->getUrl($urlParameters), $this->getSession()->getCurrentUrl())); 103 | } 104 | } 105 | 106 | protected function getParameter(string $name): ?string 107 | { 108 | return $this->parameters[$name] ?? null; 109 | } 110 | 111 | /** 112 | * Defines elements by returning an array with items being: 113 | * - :elementName => :cssLocator 114 | * - :elementName => [:selectorType => :locator] 115 | */ 116 | protected function getDefinedElements(): array 117 | { 118 | return []; 119 | } 120 | 121 | /** 122 | * @throws ElementNotFoundException 123 | */ 124 | protected function getElement(string $name, array $parameters = []): NodeElement 125 | { 126 | $element = $this->createElement($name, $parameters); 127 | 128 | if (!$this->getDocument()->has('xpath', $element->getXpath())) { 129 | throw new ElementNotFoundException( 130 | $this->getSession(), 131 | sprintf('Element named "%s" with parameters %s', $name, implode(', ', $parameters)), 132 | 'xpath', 133 | $element->getXpath() 134 | ); 135 | } 136 | 137 | return $element; 138 | } 139 | 140 | protected function hasElement(string $name, array $parameters = []): bool 141 | { 142 | return $this->getDocument()->has('xpath', $this->createElement($name, $parameters)->getXpath()); 143 | } 144 | 145 | protected function getSession(): Session 146 | { 147 | return $this->session; 148 | } 149 | 150 | protected function getDriver(): DriverInterface 151 | { 152 | return $this->session->getDriver(); 153 | } 154 | 155 | protected function getDocument(): DocumentElement 156 | { 157 | if (null === $this->document) { 158 | $this->document = new DocumentElement($this->session); 159 | } 160 | 161 | return $this->document; 162 | } 163 | 164 | private function createElement(string $name, array $parameters = []): NodeElement 165 | { 166 | $definedElements = $this->getDefinedElements(); 167 | 168 | if (!isset($definedElements[$name])) { 169 | throw new \InvalidArgumentException(sprintf( 170 | 'Could not find a defined element with name "%s". The defined ones are: %s.', 171 | $name, 172 | implode(', ', array_keys($definedElements)) 173 | )); 174 | } 175 | 176 | $elementSelector = $this->resolveParameters($name, $parameters, $definedElements); 177 | 178 | return new NodeElement( 179 | $this->getSelectorAsXpath($elementSelector, $this->session->getSelectorsHandler()), 180 | $this->session 181 | ); 182 | } 183 | 184 | /** 185 | * @param string|array $selector 186 | */ 187 | private function getSelectorAsXpath($selector, SelectorsHandler $selectorsHandler): string 188 | { 189 | $selectorType = is_array($selector) ? key($selector) : 'css'; 190 | $locator = is_array($selector) ? $selector[$selectorType] : $selector; 191 | 192 | return $selectorsHandler->selectorToXpath($selectorType, $locator); 193 | } 194 | 195 | private function resolveParameters(string $name, array $parameters, array $definedElements): string 196 | { 197 | if (!is_array($definedElements[$name])) { 198 | return strtr($definedElements[$name], $parameters); 199 | } 200 | 201 | array_map( 202 | function ($definedElement) use ($parameters) { 203 | return strtr($definedElement, $parameters); 204 | }, $definedElements[$name] 205 | ); 206 | 207 | return $definedElements[$name]; 208 | } 209 | } 210 | --------------------------------------------------------------------------------