|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/* |
|
4
|
|
|
* This file is part of the Sylius package. |
|
5
|
|
|
* |
|
6
|
|
|
* (c) Paweł Jędrzejewski |
|
7
|
|
|
* |
|
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
|
9
|
|
|
* file that was distributed with this source code. |
|
10
|
|
|
*/ |
|
11
|
|
|
|
|
12
|
|
|
declare(strict_types=1); |
|
13
|
|
|
|
|
14
|
|
|
namespace Sylius\Behat\Page\Shop\Product; |
|
15
|
|
|
|
|
16
|
|
|
use Behat\Mink\Driver\Selenium2Driver; |
|
17
|
|
|
use Behat\Mink\Element\NodeElement; |
|
18
|
|
|
use Behat\Mink\Exception\ElementNotFoundException; |
|
19
|
|
|
use DMore\ChromeDriver\ChromeDriver; |
|
20
|
|
|
use FriendsOfBehat\PageObjectExtension\Page\SymfonyPage; |
|
21
|
|
|
use FriendsOfBehat\PageObjectExtension\Page\UnexpectedPageException; |
|
22
|
|
|
use Sylius\Behat\Service\JQueryHelper; |
|
23
|
|
|
use Sylius\Component\Product\Model\ProductInterface; |
|
24
|
|
|
use Sylius\Component\Product\Model\ProductOptionInterface; |
|
25
|
|
|
use Webmozart\Assert\Assert; |
|
26
|
|
|
|
|
27
|
|
|
class ShowPage extends SymfonyPage implements ShowPageInterface |
|
28
|
|
|
{ |
|
29
|
|
|
public function getRouteName(): string |
|
30
|
|
|
{ |
|
31
|
|
|
return 'sylius_shop_product_show'; |
|
32
|
|
|
} |
|
33
|
|
|
|
|
34
|
|
|
public function addToCart(): void |
|
35
|
|
|
{ |
|
36
|
|
|
$this->getElement('add_to_cart_button')->click(); |
|
37
|
|
|
|
|
38
|
|
|
if ($this->getDriver() instanceof Selenium2Driver || $this->getDriver() instanceof ChromeDriver) { |
|
|
|
|
|
|
39
|
|
|
JQueryHelper::waitForAsynchronousActionsToFinish($this->getSession()); |
|
40
|
|
|
} |
|
41
|
|
|
} |
|
42
|
|
|
|
|
43
|
|
|
public function addToCartWithQuantity(string $quantity): void |
|
44
|
|
|
{ |
|
45
|
|
|
$this->getElement('quantity')->setValue($quantity); |
|
46
|
|
|
$this->getElement('add_to_cart_button')->click(); |
|
47
|
|
|
|
|
48
|
|
|
if ($this->getDriver() instanceof Selenium2Driver || $this->getDriver() instanceof ChromeDriver) { |
|
|
|
|
|
|
49
|
|
|
JQueryHelper::waitForAsynchronousActionsToFinish($this->getSession()); |
|
50
|
|
|
} |
|
51
|
|
|
} |
|
52
|
|
|
|
|
53
|
|
|
public function addToCartWithVariant(string $variant): void |
|
54
|
|
|
{ |
|
55
|
|
|
$this->selectVariant($variant); |
|
56
|
|
|
|
|
57
|
|
|
$this->getElement('add_to_cart_button')->click(); |
|
58
|
|
|
|
|
59
|
|
|
if ($this->getDriver() instanceof Selenium2Driver || $this->getDriver() instanceof ChromeDriver) { |
|
|
|
|
|
|
60
|
|
|
JQueryHelper::waitForAsynchronousActionsToFinish($this->getSession()); |
|
61
|
|
|
} |
|
62
|
|
|
} |
|
63
|
|
|
|
|
64
|
|
|
public function addToCartWithOption(ProductOptionInterface $option, string $optionValue): void |
|
65
|
|
|
{ |
|
66
|
|
|
$select = $this->getElement('option_select', ['%optionCode%' => $option->getCode()]); |
|
67
|
|
|
|
|
68
|
|
|
$this->getDocument()->selectFieldOption($select->getAttribute('name'), $optionValue); |
|
69
|
|
|
$this->getElement('add_to_cart_button')->click(); |
|
70
|
|
|
} |
|
71
|
|
|
|
|
72
|
|
|
public function getAttributeByName(string $name): ?string |
|
73
|
|
|
{ |
|
74
|
|
|
$attributesTable = $this->getElement('attributes'); |
|
75
|
|
|
|
|
76
|
|
|
$driver = $this->getDriver(); |
|
77
|
|
|
if ($driver instanceof Selenium2Driver || $driver instanceof ChromeDriver) { |
|
|
|
|
|
|
78
|
|
|
try { |
|
79
|
|
|
$attributesTab = $this->getElement('tab', ['%name%' => 'attributes']); |
|
80
|
|
|
if (!$attributesTab->hasAttribute('[data-test-active]')) { |
|
81
|
|
|
$attributesTab->click(); |
|
82
|
|
|
} |
|
83
|
|
|
} catch (ElementNotFoundException $exception) { |
|
|
|
|
|
|
84
|
|
|
return null; |
|
85
|
|
|
} |
|
86
|
|
|
} |
|
87
|
|
|
|
|
88
|
|
|
$nameTd = $attributesTable->find('css', sprintf('[data-test-product-attribute-name="%s"]', $name)); |
|
89
|
|
|
|
|
90
|
|
|
if (null === $nameTd) { |
|
91
|
|
|
return null; |
|
92
|
|
|
} |
|
93
|
|
|
|
|
94
|
|
|
$row = $nameTd->getParent(); |
|
95
|
|
|
|
|
96
|
|
|
return trim($row->find('css', '[data-test-product-attribute-value]')->getText()); |
|
97
|
|
|
} |
|
98
|
|
|
|
|
99
|
|
|
public function getAttributes(): array |
|
100
|
|
|
{ |
|
101
|
|
|
$attributesTable = $this->getElement('attributes'); |
|
102
|
|
|
|
|
103
|
|
|
return $attributesTable->findAll('css', '[data-test-product-attribute-name]'); |
|
104
|
|
|
} |
|
105
|
|
|
|
|
106
|
|
|
public function getAverageRating(): float |
|
107
|
|
|
{ |
|
108
|
|
|
return (float) $this->getElement('average_rating')->getAttribute('data-test-average-rating'); |
|
109
|
|
|
} |
|
110
|
|
|
|
|
111
|
|
|
public function getCurrentUrl(): string |
|
112
|
|
|
{ |
|
113
|
|
|
return $this->getDriver()->getCurrentUrl(); |
|
114
|
|
|
} |
|
115
|
|
|
|
|
116
|
|
|
public function getCurrentVariantName(): string |
|
117
|
|
|
{ |
|
118
|
|
|
$currentVariantRow = $this->getElement('current_variant_input')->getParent()->getParent(); |
|
119
|
|
|
|
|
120
|
|
|
return $currentVariantRow->find('css', 'td:first-child')->getText(); |
|
121
|
|
|
} |
|
122
|
|
|
|
|
123
|
|
|
public function getName(): string |
|
124
|
|
|
{ |
|
125
|
|
|
return $this->getElement('product_name')->getText(); |
|
126
|
|
|
} |
|
127
|
|
|
|
|
128
|
|
|
public function getPrice(): string |
|
129
|
|
|
{ |
|
130
|
|
|
return $this->getElement('product_price')->getText(); |
|
131
|
|
|
} |
|
132
|
|
|
|
|
133
|
|
|
public function getOriginalPrice(): string |
|
134
|
|
|
{ |
|
135
|
|
|
return $this->getElement('product_original_price')->getText(); |
|
136
|
|
|
} |
|
137
|
|
|
|
|
138
|
|
|
public function hasAddToCartButton(): bool |
|
139
|
|
|
{ |
|
140
|
|
|
if (!$this->hasElement('add_to_cart_button')) { |
|
141
|
|
|
return false; |
|
142
|
|
|
} |
|
143
|
|
|
|
|
144
|
|
|
return $this->getElement('add_to_cart_button') !== null && false === $this->getElement('add_to_cart_button')->hasAttribute('disabled'); |
|
145
|
|
|
} |
|
146
|
|
|
|
|
147
|
|
|
public function hasAssociation(string $productAssociationName): bool |
|
148
|
|
|
{ |
|
149
|
|
|
try { |
|
150
|
|
|
$this->getElement('association', ['%associationName%' => $productAssociationName]); |
|
151
|
|
|
} catch (ElementNotFoundException $e) { |
|
|
|
|
|
|
152
|
|
|
return false; |
|
153
|
|
|
} |
|
154
|
|
|
|
|
155
|
|
|
return true; |
|
156
|
|
|
} |
|
157
|
|
|
|
|
158
|
|
|
public function hasProductInAssociation(string $productName, string $productAssociationName): bool |
|
159
|
|
|
{ |
|
160
|
|
|
$products = $this->getElement('association', ['%associationName%' => $productAssociationName]); |
|
161
|
|
|
|
|
162
|
|
|
Assert::notNull($products); |
|
163
|
|
|
|
|
164
|
|
|
return $productName === $products->find('css', sprintf('[data-test-product-name="%s"]', $productName))->getText(); |
|
165
|
|
|
} |
|
166
|
|
|
|
|
167
|
|
|
public function hasProductOutOfStockValidationMessage(ProductInterface $product): bool |
|
168
|
|
|
{ |
|
169
|
|
|
$message = sprintf('%s does not have sufficient stock.', $product->getName()); |
|
170
|
|
|
|
|
171
|
|
|
if (!$this->hasElement('validation_errors')) { |
|
172
|
|
|
return false; |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
return $this->getElement('validation_errors')->getText() === $message; |
|
176
|
|
|
} |
|
177
|
|
|
|
|
178
|
|
|
public function hasReviewTitled(string $title): bool |
|
179
|
|
|
{ |
|
180
|
|
|
try { |
|
181
|
|
|
$element = $this->getElement('reviews_comment', ['%title%' => $title]); |
|
182
|
|
|
} catch (ElementNotFoundException $e) { |
|
|
|
|
|
|
183
|
|
|
return false; |
|
184
|
|
|
} |
|
185
|
|
|
|
|
186
|
|
|
return $title === $element->getAttribute('data-test-comment'); |
|
187
|
|
|
} |
|
188
|
|
|
|
|
189
|
|
|
public function isOutOfStock(): bool |
|
190
|
|
|
{ |
|
191
|
|
|
return $this->hasElement('out_of_stock'); |
|
192
|
|
|
} |
|
193
|
|
|
|
|
194
|
|
|
public function isMainImageDisplayed(): bool |
|
195
|
|
|
{ |
|
196
|
|
|
if (!$this->hasElement('main_image')) { |
|
197
|
|
|
return false; |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
|
|
$imageUrl = $this->getElement('main_image')->getAttribute('src'); |
|
201
|
|
|
$this->getDriver()->visit($imageUrl); |
|
202
|
|
|
$pageText = $this->getDocument()->getText(); |
|
203
|
|
|
$this->getDriver()->back(); |
|
204
|
|
|
|
|
205
|
|
|
return false === stripos($pageText, '404 Not Found'); |
|
206
|
|
|
} |
|
207
|
|
|
|
|
208
|
|
|
public function countReviews(): int |
|
209
|
|
|
{ |
|
210
|
|
|
return count($this->getElement('reviews')->findAll('css', '[data-test-comment]')); |
|
211
|
|
|
} |
|
212
|
|
|
|
|
213
|
|
|
public function selectOption(string $optionCode, string $optionValue): void |
|
214
|
|
|
{ |
|
215
|
|
|
$optionElement = $this->getElement('option_select', ['%optionCode%' => strtoupper($optionCode)]); |
|
216
|
|
|
$optionElement->selectOption($optionValue); |
|
217
|
|
|
} |
|
218
|
|
|
|
|
219
|
|
|
public function selectVariant(string $variantName): void |
|
220
|
|
|
{ |
|
221
|
|
|
$variantRadio = $this->getElement('variant_radio', ['%variantName%' => $variantName]); |
|
222
|
|
|
|
|
223
|
|
|
$driver = $this->getDriver(); |
|
224
|
|
|
if ($driver instanceof Selenium2Driver || $driver instanceof ChromeDriver) { |
|
|
|
|
|
|
225
|
|
|
$variantRadio->click(); |
|
226
|
|
|
|
|
227
|
|
|
return; |
|
228
|
|
|
} |
|
229
|
|
|
|
|
230
|
|
|
$this->getDocument()->fillField($variantRadio->getAttribute('name'), $variantRadio->getAttribute('value')); |
|
231
|
|
|
} |
|
232
|
|
|
|
|
233
|
|
|
public function visit($url): void |
|
234
|
|
|
{ |
|
235
|
|
|
$absoluteUrl = $this->makePathAbsolute($url); |
|
236
|
|
|
$this->getDriver()->visit($absoluteUrl); |
|
237
|
|
|
} |
|
238
|
|
|
|
|
239
|
|
|
public function open(array $urlParameters = []): void |
|
240
|
|
|
{ |
|
241
|
|
|
$start = microtime(true); |
|
242
|
|
|
$end = $start + 5; |
|
243
|
|
|
do { |
|
244
|
|
|
try { |
|
245
|
|
|
parent::open($urlParameters); |
|
246
|
|
|
$isOpen = true; |
|
247
|
|
|
} catch (UnexpectedPageException $exception) { |
|
|
|
|
|
|
248
|
|
|
$isOpen = false; |
|
249
|
|
|
sleep(1); |
|
250
|
|
|
} |
|
251
|
|
|
} while (!$isOpen && microtime(true) < $end); |
|
252
|
|
|
|
|
253
|
|
|
if (!$isOpen) { |
|
254
|
|
|
throw new UnexpectedPageException(); |
|
255
|
|
|
} |
|
256
|
|
|
} |
|
257
|
|
|
|
|
258
|
|
|
public function getVariantsNames(): array |
|
259
|
|
|
{ |
|
260
|
|
|
$variantsNames = []; |
|
261
|
|
|
/** @var NodeElement $variantRow */ |
|
262
|
|
|
foreach ($this->getElement('variants_rows')->findAll('css', 'td:first-child') as $variantRow) { |
|
263
|
|
|
$variantsNames[] = $variantRow->getText(); |
|
264
|
|
|
} |
|
265
|
|
|
|
|
266
|
|
|
return $variantsNames; |
|
267
|
|
|
} |
|
268
|
|
|
|
|
269
|
|
|
public function getOptionValues(string $optionCode): array |
|
270
|
|
|
{ |
|
271
|
|
|
$optionElement = $this->getElement('option_select', ['%optionCode%' => strtoupper($optionCode)]); |
|
272
|
|
|
|
|
273
|
|
|
return array_map( |
|
274
|
|
|
function (NodeElement $element) { |
|
275
|
|
|
return $element->getText(); |
|
276
|
|
|
}, |
|
277
|
|
|
$optionElement->findAll('css', 'option') |
|
278
|
|
|
); |
|
279
|
|
|
} |
|
280
|
|
|
|
|
281
|
|
|
protected function getDefinedElements(): array |
|
282
|
|
|
{ |
|
283
|
|
|
return array_merge(parent::getDefinedElements(), [ |
|
284
|
|
|
'add_to_cart_button' => '[data-test-add-to-cart-button]', |
|
285
|
|
|
'association' => '[data-test-product-association="%associationName%"]', |
|
286
|
|
|
'attributes' => '[data-test-product-attributes]', |
|
287
|
|
|
'average_rating' => '[data-test-average-rating]', |
|
288
|
|
|
'current_variant_input' => '[data-test-product-variants] td input:checked', |
|
289
|
|
|
'main_image' => '[data-test-main-image]', |
|
290
|
|
|
'name' => '[data-test-product-name]', |
|
291
|
|
|
'option_select' => '#sylius_add_to_cart_cartItem_variant_%optionCode%', |
|
292
|
|
|
'out_of_stock' => '[data-test-product-out-of-stock]', |
|
293
|
|
|
'product_price' => '[data-test-product-price]', |
|
294
|
|
|
'product_original_price' => '[data-test-product-original-price]', |
|
295
|
|
|
'product_name' => '[data-test-product-name]', |
|
296
|
|
|
'reviews' => '[data-test-product-reviews]', |
|
297
|
|
|
'reviews_comment' => '[data-test-comment="%title%"]', |
|
298
|
|
|
'selecting_variants' => '[data-test-product-selecting-variant]', |
|
299
|
|
|
'tab' => '[data-test-tab="%name%"]', |
|
300
|
|
|
'quantity' => '[data-test-quantity]', |
|
301
|
|
|
'validation_errors' => '[data-test-cart-validation-error]', |
|
302
|
|
|
'variant_radio' => '[data-test-product-variants] tbody tr:contains("%variantName%") input', |
|
303
|
|
|
'variants_rows' => '[data-test-product-variants-row]', |
|
304
|
|
|
]); |
|
305
|
|
|
} |
|
306
|
|
|
} |
|
307
|
|
|
|
This error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.jsonfile (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.jsonto be in the root folder of your repository.Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the
requireorrequire-devsection?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceofchecks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.