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\Checkout; |
15
|
|
|
|
16
|
|
|
use Behat\Mink\Driver\Selenium2Driver; |
17
|
|
|
use Behat\Mink\Element\NodeElement; |
18
|
|
|
use Behat\Mink\Session; |
19
|
|
|
use FriendsOfBehat\PageObjectExtension\Page\SymfonyPage; |
20
|
|
|
use Sylius\Behat\Service\Accessor\TableAccessorInterface; |
21
|
|
|
use Sylius\Component\Core\Model\AddressInterface; |
22
|
|
|
use Sylius\Component\Core\Model\ProductInterface; |
23
|
|
|
use Sylius\Component\Core\Model\ShippingMethodInterface; |
24
|
|
|
use Symfony\Component\Intl\Intl; |
25
|
|
|
use Symfony\Component\Routing\RouterInterface; |
26
|
|
|
|
27
|
|
|
class CompletePage extends SymfonyPage implements CompletePageInterface |
28
|
|
|
{ |
29
|
|
|
/** @var TableAccessorInterface */ |
30
|
|
|
private $tableAccessor; |
31
|
|
|
|
32
|
|
|
public function __construct( |
33
|
|
|
Session $session, |
34
|
|
|
$minkParameters, |
35
|
|
|
RouterInterface $router, |
36
|
|
|
TableAccessorInterface $tableAccessor |
37
|
|
|
) { |
38
|
|
|
parent::__construct($session, $minkParameters, $router); |
39
|
|
|
|
40
|
|
|
$this->tableAccessor = $tableAccessor; |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
public function getRouteName(): string |
44
|
|
|
{ |
45
|
|
|
return 'sylius_shop_checkout_complete'; |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
public function hasItemWithProductAndQuantity(string $productName, string $quantity): bool |
49
|
|
|
{ |
50
|
|
|
$table = $this->getElement('items_table'); |
51
|
|
|
|
52
|
|
|
try { |
53
|
|
|
$this->tableAccessor->getRowWithFields($table, ['item' => $productName, 'qty' => $quantity]); |
54
|
|
|
} catch (\InvalidArgumentException $exception) { |
55
|
|
|
return false; |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
return true; |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
public function hasShippingAddress(AddressInterface $address): bool |
62
|
|
|
{ |
63
|
|
|
$shippingAddress = $this->getElement('shipping_address')->getText(); |
64
|
|
|
|
65
|
|
|
return $this->isAddressValid($shippingAddress, $address); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
public function hasBillingAddress(AddressInterface $address): bool |
69
|
|
|
{ |
70
|
|
|
$billingAddress = $this->getElement('billing_address')->getText(); |
71
|
|
|
|
72
|
|
|
return $this->isAddressValid($billingAddress, $address); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
public function hasShippingMethod(ShippingMethodInterface $shippingMethod): bool |
76
|
|
|
{ |
77
|
|
|
if (!$this->hasElement('shipping_method')) { |
78
|
|
|
return false; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
return false !== strpos($this->getElement('shipping_method')->getText(), $shippingMethod->getName()); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
public function getPaymentMethodName(): string |
85
|
|
|
{ |
86
|
|
|
return $this->getElement('payment_method')->getText(); |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
public function hasPaymentMethod(): bool |
90
|
|
|
{ |
91
|
|
|
return $this->hasElement('payment_method'); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
public function hasProductDiscountedUnitPriceBy(ProductInterface $product, int $amount): bool |
95
|
|
|
{ |
96
|
|
|
$priceWithoutDiscount = $this->getPriceFromString($this->getElement('product_old_price', ['%name%' => $product->getName()])->getText()); |
97
|
|
|
$priceWithDiscount = $this->getPriceFromString($this->getElement('product_unit_price', ['%name%' => $product->getName()])->getText()); |
98
|
|
|
$discount = $priceWithoutDiscount - $priceWithDiscount; |
99
|
|
|
|
100
|
|
|
return $discount === $amount; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
public function hasOrderTotal(int $total): bool |
104
|
|
|
{ |
105
|
|
|
if (!$this->hasElement('order_total')) { |
106
|
|
|
return false; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
return $this->getTotalFromString($this->getElement('order_total')->getText()) === $total; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
public function getBaseCurrencyOrderTotal(): string |
113
|
|
|
{ |
114
|
|
|
return (string) $this->getBaseTotalFromString($this->getElement('base_order_total')->getText()); |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
public function addNotes(string $notes): void |
118
|
|
|
{ |
119
|
|
|
$this->getElement('extra_notes')->setValue($notes); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
public function hasPromotionTotal(string $promotionTotal): bool |
123
|
|
|
{ |
124
|
|
|
return false !== strpos($this->getElement('promotion_total')->getText(), $promotionTotal); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
public function hasPromotion(string $promotionName): bool |
128
|
|
|
{ |
129
|
|
|
return false !== stripos($this->getElement('promotion_discounts')->getText(), $promotionName); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
public function hasShippingPromotion(string $promotionName): bool |
133
|
|
|
{ |
134
|
|
|
/** @var NodeElement $shippingPromotions */ |
135
|
|
|
$shippingPromotions = $this->getElement('promotions_shipping_details'); |
136
|
|
|
|
137
|
|
|
return false !== strpos($shippingPromotions->getAttribute('data-html'), $promotionName); |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
public function getTaxTotal(): string |
141
|
|
|
{ |
142
|
|
|
return $this->getElement('tax_total')->getText(); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
public function getShippingTotal(): string |
146
|
|
|
{ |
147
|
|
|
return $this->getElement('shipping_total')->getText(); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
public function hasShippingTotal(): bool |
151
|
|
|
{ |
152
|
|
|
return $this->hasElement('shipping_total'); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
public function hasProductUnitPrice(ProductInterface $product, string $price): bool |
156
|
|
|
{ |
157
|
|
|
return $this->getPriceFromString($this->getElement('product_unit_price', ['%name%' => $product->getName()])->getText()) === $this->getPriceFromString($price); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
public function hasProductOutOfStockValidationMessage(ProductInterface $product): bool |
161
|
|
|
{ |
162
|
|
|
$message = sprintf('%s does not have sufficient stock.', $product->getName()); |
163
|
|
|
|
164
|
|
|
return $this->getElement('validation_errors')->getText() === $message; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
public function getValidationErrors(): string |
168
|
|
|
{ |
169
|
|
|
return $this->getElement('validation_errors')->getText(); |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
public function hasLocale(string $localeName): bool |
173
|
|
|
{ |
174
|
|
|
return false !== strpos($this->getElement('locale')->getText(), $localeName); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
public function hasCurrency(string $currencyCode): bool |
178
|
|
|
{ |
179
|
|
|
return false !== strpos($this->getElement('currency')->getText(), $currencyCode); |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
public function confirmOrder(): void |
183
|
|
|
{ |
184
|
|
|
$this->getElement('confirm_button')->press(); |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
public function changeAddress(): void |
188
|
|
|
{ |
189
|
|
|
$this->getElement('addressing_step_label')->click(); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
public function changeShippingMethod(): void |
193
|
|
|
{ |
194
|
|
|
$this->getElement('shipping_step_label')->click(); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
public function changePaymentMethod(): void |
198
|
|
|
{ |
199
|
|
|
$this->getElement('payment_step_label')->click(); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
public function hasShippingProvinceName(string $provinceName): bool |
203
|
|
|
{ |
204
|
|
|
$shippingAddressText = $this->getElement('shipping_address')->getText(); |
205
|
|
|
|
206
|
|
|
return false !== stripos($shippingAddressText, $provinceName); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
public function hasBillingProvinceName(string $provinceName): bool |
210
|
|
|
{ |
211
|
|
|
$billingAddressText = $this->getElement('billing_address')->getText(); |
212
|
|
|
|
213
|
|
|
return false !== stripos($billingAddressText, $provinceName); |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
public function hasShippingPromotionWithDiscount(string $promotionName, string $discount): bool |
217
|
|
|
{ |
218
|
|
|
$promotionWithDiscount = sprintf('%s: %s', $promotionName, $discount); |
219
|
|
|
|
220
|
|
|
/** @var NodeElement $shippingPromotions */ |
221
|
|
|
$shippingPromotions = $this->getElement('promotions_shipping_details'); |
222
|
|
|
|
223
|
|
|
return false !== strpos($shippingPromotions->getAttribute('data-html'), $promotionWithDiscount); |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
public function hasOrderPromotion(string $promotionName): bool |
227
|
|
|
{ |
228
|
|
|
/** @var NodeElement $shippingPromotions */ |
229
|
|
|
$shippingPromotions = $this->getElement('order_promotions_details'); |
230
|
|
|
|
231
|
|
|
return false !== strpos($shippingPromotions->getAttribute('data-html'), $promotionName); |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
public function tryToOpen(array $urlParameters = []): void |
235
|
|
|
{ |
236
|
|
|
if ($this->getDriver() instanceof Selenium2Driver) { |
237
|
|
|
$start = microtime(true); |
238
|
|
|
$end = $start + 15; |
239
|
|
|
do { |
240
|
|
|
parent::tryToOpen($urlParameters); |
241
|
|
|
sleep(3); |
242
|
|
|
} while (!$this->isOpen() && microtime(true) < $end); |
243
|
|
|
|
244
|
|
|
return; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
parent::tryToOpen($urlParameters); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
protected function getDefinedElements(): array |
251
|
|
|
{ |
252
|
|
|
return array_merge(parent::getDefinedElements(), [ |
253
|
|
|
'addressing_step_label' => '[data-test-step-address]', |
254
|
|
|
'base_order_total' => '[data-test-summary-order-total]', |
255
|
|
|
'billing_address' => '[data-test-billing-address]', |
256
|
|
|
'confirm_button' => '[data-test-confirmation-button]', |
257
|
|
|
'currency' => '[data-test-order-currency-code]', |
258
|
|
|
'extra_notes' => '[data-test-extra-notes]', |
259
|
|
|
'items_table' => '[data-test-order-table]', |
260
|
|
|
'locale' => '[data-test-order-locale-name]', |
261
|
|
|
'order_promotions_details' => '[data-test-order-promotions-details]', |
262
|
|
|
'order_total' => '[data-test-order-total]', |
263
|
|
|
'payment_method' => '[data-test-payment-method]', |
264
|
|
|
'payment_step_label' => '[data-test-step-payment]', |
265
|
|
|
'product_old_price' => '[data-test-product-old-price="%name%"]', |
266
|
|
|
'product_row' => '[data-test-product-row="%name%"]', |
267
|
|
|
'product_unit_price' => '[data-test-product-unit-price="%name%"]', |
268
|
|
|
'promotion_discounts' => '[data-test-promotion-discounts]', |
269
|
|
|
'promotion_shipping_discounts' => '[data-test-promotion-shipping-discounts]', |
270
|
|
|
'promotion_total' => '[data-test-promotion-total]', |
271
|
|
|
'promotions_shipping_details' => '[data-test-shipping-promotion-details]', |
272
|
|
|
'shipping_address' => '[data-test-shipping-address]', |
273
|
|
|
'shipping_method' => '[data-test-shipping-method]', |
274
|
|
|
'shipping_step_label' => '[data-test-step-shipping]', |
275
|
|
|
'shipping_total' => '[data-test-shipping-total]', |
276
|
|
|
'tax_total' => '[data-test-tax-total]', |
277
|
|
|
'validation_errors' => '[data-test-validation-error]', |
278
|
|
|
]); |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
private function isAddressValid(string $displayedAddress, AddressInterface $address): bool |
282
|
|
|
{ |
283
|
|
|
return |
284
|
|
|
$this->hasAddressPart($displayedAddress, $address->getCompany(), true) && |
285
|
|
|
$this->hasAddressPart($displayedAddress, $address->getFirstName()) && |
286
|
|
|
$this->hasAddressPart($displayedAddress, $address->getLastName()) && |
287
|
|
|
$this->hasAddressPart($displayedAddress, $address->getPhoneNumber(), true) && |
288
|
|
|
$this->hasAddressPart($displayedAddress, $address->getStreet()) && |
289
|
|
|
$this->hasAddressPart($displayedAddress, $address->getCity()) && |
290
|
|
|
$this->hasAddressPart($displayedAddress, $address->getProvinceCode(), true) && |
291
|
|
|
$this->hasAddressPart($displayedAddress, $this->getCountryName($address->getCountryCode())) && |
292
|
|
|
$this->hasAddressPart($displayedAddress, $address->getPostcode()) |
293
|
|
|
; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
private function hasAddressPart(string $address, ?string $addressPart, bool $optional = false): bool |
297
|
|
|
{ |
298
|
|
|
if ($optional && null === $addressPart) { |
299
|
|
|
return true; |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
return false !== strpos($address, $addressPart); |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
private function getCountryName(string $countryCode): string |
306
|
|
|
{ |
307
|
|
|
return strtoupper(Intl::getRegionBundle()->getCountryName($countryCode, 'en')); |
|
|
|
|
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
private function getPriceFromString(string $price): int |
311
|
|
|
{ |
312
|
|
|
return (int) round((float) str_replace(['€', '£', '$'], '', $price) * 100, 2); |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
private function getTotalFromString(string $total): int |
316
|
|
|
{ |
317
|
|
|
$total = str_replace('Total:', '', $total); |
318
|
|
|
|
319
|
|
|
return $this->getPriceFromString($total); |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
private function getBaseTotalFromString(string $total): int |
323
|
|
|
{ |
324
|
|
|
$total = str_replace('Total in base currency:', '', $total); |
325
|
|
|
|
326
|
|
|
return $this->getPriceFromString($total); |
327
|
|
|
} |
328
|
|
|
} |
329
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.