@roomstay/frontend 2.6.19 → 2.6.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/166.bundle.js +1 -1
- package/dist/279.bundle.js +1 -1
- package/dist/370.bundle.js +1 -1
- package/dist/449.bundle.js +1 -1
- package/dist/586.bundle.js +1 -0
- package/dist/736.bundle.js +1 -1
- package/dist/873.bundle.js +1 -1
- package/dist/903.bundle.js +1 -1
- package/dist/927.bundle.js +1 -0
- package/dist/972.bundle.js +1 -1
- package/dist/978.bundle.js +1 -0
- package/dist/e2e/data/checkoutTestData.d.ts +16 -0
- package/dist/e2e/data/checkoutTestData.js +32 -0
- package/dist/e2e/data/checkoutTestData.js.map +1 -0
- package/dist/e2e/goBackAndCheckoutAgain.spec.d.ts +1 -0
- package/dist/e2e/goBackAndCheckoutAgain.spec.js +72 -0
- package/dist/e2e/goBackAndCheckoutAgain.spec.js.map +1 -0
- package/dist/e2e/helpers/checkoutHelpers.d.ts +7 -0
- package/dist/e2e/helpers/checkoutHelpers.js +164 -0
- package/dist/e2e/helpers/checkoutHelpers.js.map +1 -0
- package/dist/e2e/price.spec.d.ts +1 -0
- package/dist/e2e/price.spec.js +154 -0
- package/dist/e2e/price.spec.js.map +1 -0
- package/dist/main.bundle.js +1 -1
- package/dist/src/api/MemberAPI.d.ts +2 -3
- package/dist/src/api/MemberAPI.js +2 -6
- package/dist/src/api/MemberAPI.js.map +1 -1
- package/dist/src/components/User/Forms/ForgotPasswordForm.js +6 -2
- package/dist/src/components/User/Forms/ForgotPasswordForm.js.map +1 -1
- package/dist/src/components/generic/InputGroup/InputGroup.js +6 -0
- package/dist/src/components/generic/InputGroup/InputGroup.js.map +1 -1
- package/dist/src/components/generic/InputGroup/InputGroup.types.d.ts +1 -1
- package/dist/src/components/generic/InputGroup/InputGroup.types.js.map +1 -1
- package/dist/src/components/generic/Select/InputSelect.js +1 -1
- package/dist/src/components/generic/Select/InputSelect.js.map +1 -1
- package/dist/src/components/members/EditMyProfile.js +3 -3
- package/dist/src/components/members/EditMyProfile.js.map +1 -1
- package/dist/src/components/steps/addons/AddonList.js +2 -2
- package/dist/src/components/steps/addons/AddonList.js.map +1 -1
- package/dist/src/components/steps/addons/AddonsNotFoundBlock.d.ts +2 -0
- package/dist/src/components/steps/addons/{AddonsNotFound.js → AddonsNotFoundBlock.js} +9 -3
- package/dist/src/components/steps/addons/AddonsNotFoundBlock.js.map +1 -0
- package/dist/src/components/steps/confirmation/RoomContactDetails.js +53 -29
- package/dist/src/components/steps/confirmation/RoomContactDetails.js.map +1 -1
- package/dist/src/components/steps/confirmation/StepConfirmationAcknowledgement.js +3 -1
- package/dist/src/components/steps/confirmation/StepConfirmationAcknowledgement.js.map +1 -1
- package/dist/src/components/steps/confirmation/StepConfirmationForm.js +1 -1
- package/dist/src/components/steps/confirmation/StepConfirmationForm.js.map +1 -1
- package/dist/src/components/steps/room/MemberSignInModal/EmailAndFullnameMemberModal.d.ts +4 -0
- package/dist/src/components/steps/room/MemberSignInModal/EmailAndFullnameMemberModal.js +37 -0
- package/dist/src/components/steps/room/MemberSignInModal/EmailAndFullnameMemberModal.js.map +1 -0
- package/dist/src/components/steps/room/MemberSignInModal/ExternalMemberModal.d.ts +16 -5
- package/dist/src/components/steps/room/MemberSignInModal/ExternalMemberModal.js +51 -54
- package/dist/src/components/steps/room/MemberSignInModal/ExternalMemberModal.js.map +1 -1
- package/dist/src/components/steps/room/MemberSignInModal/FamilyNameMemberModal.d.ts +8 -0
- package/dist/src/components/steps/room/MemberSignInModal/FamilyNameMemberModal.js +27 -0
- package/dist/src/components/steps/room/MemberSignInModal/FamilyNameMemberModal.js.map +1 -0
- package/dist/src/components/steps/room/MemberSignInModal/InternalMemberSignInModal.d.ts +6 -0
- package/dist/src/components/steps/room/MemberSignInModal/InternalMemberSignInModal.js +318 -0
- package/dist/src/components/steps/room/MemberSignInModal/InternalMemberSignInModal.js.map +1 -0
- package/dist/src/components/steps/room/MemberSignInModal/{MemberSignInModal.types.d.ts → InternalMemberSignInModal.types.d.ts} +1 -1
- package/dist/src/components/steps/room/MemberSignInModal/{MemberSignInModal.types.js → InternalMemberSignInModal.types.js} +1 -1
- package/dist/src/components/steps/room/MemberSignInModal/InternalMemberSignInModal.types.js.map +1 -0
- package/dist/src/components/steps/room/MemberSignInModal/MemberSignInModal.d.ts +1 -6
- package/dist/src/components/steps/room/MemberSignInModal/MemberSignInModal.js +28 -284
- package/dist/src/components/steps/room/MemberSignInModal/MemberSignInModal.js.map +1 -1
- package/dist/src/components/steps/room/StepRoomBestRateAlert.js +22 -3
- package/dist/src/components/steps/room/StepRoomBestRateAlert.js.map +1 -1
- package/dist/src/contexts/BasketContext/BasketContextWrapper.js +9 -6
- package/dist/src/contexts/BasketContext/BasketContextWrapper.js.map +1 -1
- package/dist/src/contexts/CompanyContext/CompanyContextType.type.d.ts +9 -3
- package/dist/src/contexts/CompanyContext/CompanyContextType.type.js.map +1 -1
- package/dist/src/contexts/CompanyContext/CompanyContextWrapper.js +51 -44
- package/dist/src/contexts/CompanyContext/CompanyContextWrapper.js.map +1 -1
- package/dist/src/contexts/ConfirmationStepContext/ConfirmationStepContextWrapper.js +5 -3
- package/dist/src/contexts/ConfirmationStepContext/ConfirmationStepContextWrapper.js.map +1 -1
- package/dist/src/contexts/Members/AuthenticationContext/AuthenticationContext.d.ts +8 -1
- package/dist/src/contexts/Members/AuthenticationContext/AuthenticationContext.js +2 -0
- package/dist/src/contexts/Members/AuthenticationContext/AuthenticationContext.js.map +1 -1
- package/dist/src/contexts/Members/AuthenticationContext/AuthenticationContextProvider.js +16 -4
- package/dist/src/contexts/Members/AuthenticationContext/AuthenticationContextProvider.js.map +1 -1
- package/dist/src/contexts/Members/RoomstayMemberContext/RoomstayMemberContextProvider.js +2 -12
- package/dist/src/contexts/Members/RoomstayMemberContext/RoomstayMemberContextProvider.js.map +1 -1
- package/dist/src/events/{views → actions}/PlacedBookingEvent.d.ts +3 -1
- package/dist/src/events/{views → actions}/PlacedBookingEvent.js +2 -1
- package/dist/src/events/actions/PlacedBookingEvent.js.map +1 -0
- package/dist/src/events/views/StepThanksViewEvent.d.ts +3 -1
- package/dist/src/events/views/StepThanksViewEvent.js +3 -1
- package/dist/src/events/views/StepThanksViewEvent.js.map +1 -1
- package/dist/src/hooks/useExternalMember.d.ts +1 -1
- package/dist/src/hooks/useNextStepAction.js +1 -1
- package/dist/src/hooks/useNextStepAction.js.map +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +9 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/models/Api/HotelDTO.d.ts +2 -6
- package/dist/src/models/Api/HotelDTO.js +1 -7
- package/dist/src/models/Api/HotelDTO.js.map +1 -1
- package/dist/src/models/BasketRow.d.ts +3 -3
- package/dist/src/models/BasketRow.js +0 -3
- package/dist/src/models/BasketRow.js.map +1 -1
- package/dist/src/models/Client/Hotel/Hotel.d.ts +5 -4
- package/dist/src/models/Client/Hotel/Hotel.js.map +1 -1
- package/dist/src/models/Confirmation.d.ts +3 -0
- package/dist/src/models/Confirmation.js +2 -0
- package/dist/src/models/Confirmation.js.map +1 -1
- package/dist/src/models/RoomstaySession.d.ts +5 -0
- package/dist/src/models/RoomstaySession.js.map +1 -1
- package/dist/src/pages/account/AccountRouter.js +2 -0
- package/dist/src/pages/account/AccountRouter.js.map +1 -1
- package/dist/src/pages/account/AccountRoutes.d.ts +3 -0
- package/dist/src/pages/account/AccountRoutes.js +3 -0
- package/dist/src/pages/account/AccountRoutes.js.map +1 -1
- package/dist/src/pages/account/ChangePassword/AccountChangePasswordPage.d.ts +1 -0
- package/dist/src/pages/account/ChangePassword/AccountChangePasswordPage.js +143 -0
- package/dist/src/pages/account/ChangePassword/AccountChangePasswordPage.js.map +1 -0
- package/dist/src/pages/account/Details/AccountDetailsProfilePage.js +11 -16
- package/dist/src/pages/account/Details/AccountDetailsProfilePage.js.map +1 -1
- package/dist/src/pages/steps/StepDate/StepDateComponent.js +7 -0
- package/dist/src/pages/steps/StepDate/StepDateComponent.js.map +1 -1
- package/dist/src/pages/steps/StepThanks/StepThanksComponent.js +1 -1
- package/dist/src/pages/steps/StepThanks/StepThanksComponent.js.map +1 -1
- package/dist/src/providers/FeatureProvider.d.ts +1 -1
- package/dist/src/providers/FeatureProvider.js +6 -2
- package/dist/src/providers/FeatureProvider.js.map +1 -1
- package/dist/src/providers/SessionProvider.js +5 -1
- package/dist/src/providers/SessionProvider.js.map +1 -1
- package/dist/src/providers/feature/DerbysoftClickTrackingFeature.d.ts +7 -0
- package/dist/src/providers/feature/DerbysoftClickTrackingFeature.js +32 -0
- package/dist/src/providers/feature/DerbysoftClickTrackingFeature.js.map +1 -0
- package/dist/src/providers/feature/Feature.d.ts +3 -3
- package/dist/src/providers/feature/Feature.js +3 -3
- package/dist/src/providers/feature/Feature.js.map +1 -1
- package/dist/src/providers/feature/GuestTitleFieldFeature.d.ts +21 -0
- package/dist/src/providers/feature/GuestTitleFieldFeature.js +37 -0
- package/dist/src/providers/feature/GuestTitleFieldFeature.js.map +1 -0
- package/dist/src/providers/storage/LocalStorageProvider.js +1 -1
- package/dist/src/providers/storage/LocalStorageProvider.js.map +1 -1
- package/dist/src/translations/Translation.d.ts +19 -3
- package/dist/src/translations/Translation.js +19 -3
- package/dist/src/translations/Translation.js.map +1 -1
- package/dist/src/translations/languages/en-gb.js +20 -4
- package/dist/src/translations/languages/en-gb.js.map +1 -1
- package/dist/src/util/DerbysoftPixel.d.ts +4 -0
- package/dist/src/util/DerbysoftPixel.js +56 -0
- package/dist/src/util/DerbysoftPixel.js.map +1 -0
- package/dist/test.bundle.js +1 -1
- package/dist/tests/offline/entry/OfflineEngineDefaults.js +5 -0
- package/dist/tests/offline/entry/OfflineEngineDefaults.js.map +1 -1
- package/dist/vendors.bundle.js +1 -1
- package/dist/vendors.bundle.js.LICENSE.txt +2 -0
- package/package.json +2 -2
- package/dist/201.bundle.js +0 -1
- package/dist/457.bundle.js +0 -1
- package/dist/468.bundle.js +0 -1
- package/dist/src/components/steps/addons/AddonsNotFound.d.ts +0 -1
- package/dist/src/components/steps/addons/AddonsNotFound.js.map +0 -1
- package/dist/src/components/steps/room/MemberSignInModal/MemberSignInModal.types.js.map +0 -1
- package/dist/src/events/views/PlacedBookingEvent.js.map +0 -1
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.verifyBookingConfirmation = exports.submitBookingReservation = exports.fillPaymentInformation = exports.fillContactInfo = exports.proceedToConfirmationStep = exports.selectAvailableDates = void 0;
|
|
13
|
+
const test_1 = require("@playwright/test");
|
|
14
|
+
function selectAvailableDates(page, maxRetries = 5) {
|
|
15
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
16
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
17
|
+
// Choose the first available check-in date.
|
|
18
|
+
const checkInDateElement = page.locator('.diji-calendar-month--item.--selectable').first();
|
|
19
|
+
yield (0, test_1.expect)(checkInDateElement).toBeVisible();
|
|
20
|
+
yield checkInDateElement.click();
|
|
21
|
+
let checkOutDateElement;
|
|
22
|
+
if (attempt === maxRetries - 1) {
|
|
23
|
+
// If this is the last attempt, try a 1-night stay (this has a higher success rate).
|
|
24
|
+
checkOutDateElement = page.locator('.diji-calendar-month--item.--selectable:not(.--selected)').first();
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
// Choose a random available check-out date.
|
|
28
|
+
const checkOutDateElements = yield page.locator('.diji-calendar-month--item.--selectable:not(.--selected)').all();
|
|
29
|
+
const randomIndex = Math.floor(Math.random() * checkOutDateElements.length);
|
|
30
|
+
checkOutDateElement = checkOutDateElements[randomIndex];
|
|
31
|
+
}
|
|
32
|
+
yield (0, test_1.expect)(checkOutDateElement).toBeVisible();
|
|
33
|
+
yield checkOutDateElement.click();
|
|
34
|
+
// TODO: Instead of waiting for timeout, wait for the loading indicators on the other dates to disappear.
|
|
35
|
+
// Context: After choosing checkout date, the loading indicators appear for the dates following the checkout
|
|
36
|
+
// date. If we click 'Check Availability', we seem to briefly see an error message. This doesn't actually
|
|
37
|
+
// make the tests fail though. Keeping this in here in case it's indicative of an issue with RoomStay itself.
|
|
38
|
+
// The error is an "Oh no, something went wrong" message and shows the select room bar without any dates in it.
|
|
39
|
+
// The error quickly goes away though.
|
|
40
|
+
yield page.waitForTimeout(500);
|
|
41
|
+
// Click the 'Check Availability' button.
|
|
42
|
+
const checkAvailabilityButton = page.getByRole('button', { name: 'Check Availability' });
|
|
43
|
+
yield (0, test_1.expect)(checkAvailabilityButton).not.toBeDisabled();
|
|
44
|
+
yield checkAvailabilityButton.click();
|
|
45
|
+
// Check for error message.
|
|
46
|
+
const errorMessage = yield page.waitForSelector('text=Please check your dates and', { timeout: 1000 }).catch(() => null);
|
|
47
|
+
if (errorMessage) {
|
|
48
|
+
console.log(`Error message found on attempt ${attempt + 1}. Retrying with different dates.`);
|
|
49
|
+
const newSearchButton = page.getByRole('button', { name: 'New Search' });
|
|
50
|
+
yield (0, test_1.expect)(newSearchButton).toBeVisible();
|
|
51
|
+
yield newSearchButton.click();
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
exports.selectAvailableDates = selectAvailableDates;
|
|
61
|
+
function proceedToConfirmationStep(page) {
|
|
62
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
63
|
+
// Click the 'View Rates' button.
|
|
64
|
+
const viewRatesNowButton = page.getByRole('button', { name: 'View rates now' }).first();
|
|
65
|
+
yield (0, test_1.expect)(viewRatesNowButton).toBeVisible();
|
|
66
|
+
yield (0, test_1.expect)(viewRatesNowButton).not.toBeDisabled();
|
|
67
|
+
yield viewRatesNowButton.click();
|
|
68
|
+
// Click the first 'Book Now' button.
|
|
69
|
+
const firstBookNowButton = page.getByRole('button', { name: 'Book Now' }).first();
|
|
70
|
+
yield (0, test_1.expect)(firstBookNowButton).toBeVisible();
|
|
71
|
+
yield firstBookNowButton.click();
|
|
72
|
+
// Confirm that the 'Added xx' toast notification is visible.
|
|
73
|
+
// TOODO: Sometimes there are two notifications, but I can only reproduce this in Playwright.
|
|
74
|
+
const addedRoomNotification = page.locator('.c-notification__inner').first();
|
|
75
|
+
yield (0, test_1.expect)(addedRoomNotification).toBeVisible();
|
|
76
|
+
yield (0, test_1.expect)(addedRoomNotification).toContainText('Added');
|
|
77
|
+
// A bit hacky... Need a better way to check the current step.
|
|
78
|
+
try {
|
|
79
|
+
const isAddonsStep = yield page.waitForSelector('.step-selector__item.is-current p.text-body:has-text("Add-Ons")', { timeout: 1000 });
|
|
80
|
+
if (isAddonsStep) {
|
|
81
|
+
const nextButton = page.getByRole('button', { name: 'Next Step' }).first();
|
|
82
|
+
yield (0, test_1.expect)(nextButton).toBeVisible();
|
|
83
|
+
yield nextButton.click();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
console.log('Add-ons step not found, continuing.');
|
|
88
|
+
}
|
|
89
|
+
const confirmReservationButton = page.getByRole('button', { name: 'Confirm Reservation' });
|
|
90
|
+
yield (0, test_1.expect)(confirmReservationButton).toBeVisible();
|
|
91
|
+
yield (0, test_1.expect)(confirmReservationButton).toHaveText('Confirm Reservation');
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
exports.proceedToConfirmationStep = proceedToConfirmationStep;
|
|
95
|
+
function fillContactInfo(page, data) {
|
|
96
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
97
|
+
// Fill Contact Info fields
|
|
98
|
+
yield page.fill('input[name="userProfiles.0.Forename"]', data.firstName);
|
|
99
|
+
yield page.fill('input[name="userProfiles.0.Surname"]', data.lastName);
|
|
100
|
+
yield page.fill('input[name="userProfiles.0.Email"]', data.email);
|
|
101
|
+
yield page.fill('input[name="userProfiles.0.Phone"]', data.phone);
|
|
102
|
+
yield page.fill('input[name="userProfiles.0.Address"]', data.address);
|
|
103
|
+
yield page.fill('input[name="userProfiles.0.City"]', data.city);
|
|
104
|
+
yield page.fill('input[name="userProfiles.0.PostCode"]', data.zipCode);
|
|
105
|
+
// Country selector
|
|
106
|
+
yield page.fill('.rs-enhanced-country-field--search input', data.country);
|
|
107
|
+
yield page.waitForSelector('.rs-enhanced-country-field--suggest-list-country', { state: 'visible' });
|
|
108
|
+
yield page.click('.rs-enhanced-country-field--suggest-list-country-name'); // Assuming there's only one result...
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
exports.fillContactInfo = fillContactInfo;
|
|
112
|
+
function fillPaymentInformation(page, data) {
|
|
113
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
114
|
+
// Fill Payment Information. Each of these fields is within their own secure iframe that we need to enter first.
|
|
115
|
+
const cardNumberFrame = yield (yield page.waitForSelector('iframe[title="Secure card number input frame"]')).contentFrame();
|
|
116
|
+
if (cardNumberFrame) {
|
|
117
|
+
yield cardNumberFrame.fill('input[aria-label="Card Number Input"]', data.cardNumber);
|
|
118
|
+
}
|
|
119
|
+
const cardExpiryFrame = yield (yield page.waitForSelector('iframe[title="Secure card expiration date input frame"]')).contentFrame();
|
|
120
|
+
if (cardExpiryFrame) {
|
|
121
|
+
yield cardExpiryFrame.fill('input[aria-label="Card expiration date"]', data.cardExpiry);
|
|
122
|
+
}
|
|
123
|
+
const cardCvvFrame = yield (yield page.waitForSelector('iframe[title="Secure card security code input frame"]')).contentFrame();
|
|
124
|
+
if (cardCvvFrame) {
|
|
125
|
+
yield cardCvvFrame.fill('input[aria-label="Card Security Code"]', data.cardCVC);
|
|
126
|
+
}
|
|
127
|
+
const cardNameFrame = yield (yield page.waitForSelector('iframe[title="Secure text input frame"]')).contentFrame();
|
|
128
|
+
if (cardNameFrame) {
|
|
129
|
+
yield cardNameFrame.fill('input[aria-label="Text input"]', `${data.firstName} ${data.lastName}`);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
exports.fillPaymentInformation = fillPaymentInformation;
|
|
134
|
+
function submitBookingReservation(page) {
|
|
135
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
136
|
+
const confirmReservationButton = page.getByRole('button', { name: 'Confirm Reservation' });
|
|
137
|
+
yield (0, test_1.expect)(confirmReservationButton).toBeVisible();
|
|
138
|
+
yield confirmReservationButton.click();
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
exports.submitBookingReservation = submitBookingReservation;
|
|
142
|
+
function verifyBookingConfirmation(page, data) {
|
|
143
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
144
|
+
// We should now be on the 'Thanks' step.
|
|
145
|
+
yield page.waitForSelector('.step-selector__item.is-current p.text-body:has-text("Thanks")', { timeout: 5000 });
|
|
146
|
+
// There should be a message thanking us for our booking.
|
|
147
|
+
const thankYouMessage = page.locator('h3:has-text("Thank you")');
|
|
148
|
+
yield (0, test_1.expect)(thankYouMessage).toBeVisible();
|
|
149
|
+
// Click the 'More Details' button to show a modal with our booking details.
|
|
150
|
+
const moreDetailsButton = page.getByRole('button', { name: 'More Details' });
|
|
151
|
+
yield (0, test_1.expect)(moreDetailsButton).toBeVisible();
|
|
152
|
+
yield moreDetailsButton.click();
|
|
153
|
+
const modal = page.locator('div[class*="roomstay-modal"]').first();
|
|
154
|
+
yield (0, test_1.expect)(modal).toBeVisible();
|
|
155
|
+
// The modal should contain our name and email address.
|
|
156
|
+
yield (0, test_1.expect)(modal.locator(`text=${data.firstName} ${data.lastName}`)).toBeVisible();
|
|
157
|
+
yield (0, test_1.expect)(modal.locator(`text=${data.email}`)).toBeVisible();
|
|
158
|
+
// Close the modal.
|
|
159
|
+
const closeButton = modal.locator('i[name="Close"]');
|
|
160
|
+
yield closeButton.click();
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
exports.verifyBookingConfirmation = verifyBookingConfirmation;
|
|
164
|
+
//# sourceMappingURL=checkoutHelpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkoutHelpers.js","sourceRoot":"/","sources":["e2e/helpers/checkoutHelpers.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, Locator } from '@playwright/test';\nimport { CheckoutTestData } from '../data/checkoutTestData';\n\nexport async function selectAvailableDates(page: any, maxRetries: number = 5): Promise<boolean> {\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n // Choose the first available check-in date.\n const checkInDateElement = page.locator('.diji-calendar-month--item.--selectable').first();\n await expect(checkInDateElement).toBeVisible();\n await checkInDateElement.click();\n\n let checkOutDateElement: Locator;\n if (attempt === maxRetries - 1) {\n // If this is the last attempt, try a 1-night stay (this has a higher success rate).\n checkOutDateElement = page.locator('.diji-calendar-month--item.--selectable:not(.--selected)').first();\n } else {\n // Choose a random available check-out date.\n const checkOutDateElements = await page.locator('.diji-calendar-month--item.--selectable:not(.--selected)').all();\n const randomIndex = Math.floor(Math.random() * checkOutDateElements.length);\n checkOutDateElement = checkOutDateElements[randomIndex];\n }\n\n await expect(checkOutDateElement).toBeVisible();\n await checkOutDateElement.click();\n\n // TODO: Instead of waiting for timeout, wait for the loading indicators on the other dates to disappear.\n // Context: After choosing checkout date, the loading indicators appear for the dates following the checkout\n // date. If we click 'Check Availability', we seem to briefly see an error message. This doesn't actually\n // make the tests fail though. Keeping this in here in case it's indicative of an issue with RoomStay itself.\n // The error is an \"Oh no, something went wrong\" message and shows the select room bar without any dates in it.\n // The error quickly goes away though.\n await page.waitForTimeout(500);\n\n // Click the 'Check Availability' button.\n const checkAvailabilityButton = page.getByRole('button', { name: 'Check Availability' });\n await expect(checkAvailabilityButton).not.toBeDisabled();\n await checkAvailabilityButton.click();\n\n // Check for error message.\n const errorMessage = await page.waitForSelector('text=Please check your dates and', { timeout: 1000 }).catch(() => null);\n if (errorMessage) {\n console.log(`Error message found on attempt ${attempt + 1}. Retrying with different dates.`);\n const newSearchButton = page.getByRole('button', { name: 'New Search' });\n await expect(newSearchButton).toBeVisible();\n await newSearchButton.click();\n } else {\n return true;\n }\n }\n\n return false;\n}\n\nexport async function proceedToConfirmationStep(page: any) {\n // Click the 'View Rates' button.\n const viewRatesNowButton = page.getByRole('button', { name: 'View rates now' }).first();\n await expect(viewRatesNowButton).toBeVisible();\n await expect(viewRatesNowButton).not.toBeDisabled();\n await viewRatesNowButton.click();\n\n // Click the first 'Book Now' button.\n const firstBookNowButton = page.getByRole('button', { name: 'Book Now' }).first();\n await expect(firstBookNowButton).toBeVisible();\n await firstBookNowButton.click();\n\n // Confirm that the 'Added xx' toast notification is visible.\n // TOODO: Sometimes there are two notifications, but I can only reproduce this in Playwright.\n const addedRoomNotification = page.locator('.c-notification__inner').first();\n await expect(addedRoomNotification).toBeVisible();\n await expect(addedRoomNotification).toContainText('Added');\n\n // A bit hacky... Need a better way to check the current step.\n try {\n const isAddonsStep = await page.waitForSelector('.step-selector__item.is-current p.text-body:has-text(\"Add-Ons\")', { timeout: 1000 });\n if (isAddonsStep) {\n const nextButton = page.getByRole('button', { name: 'Next Step' }).first();\n await expect(nextButton).toBeVisible();\n await nextButton.click();\n }\n } catch (error) {\n console.log('Add-ons step not found, continuing.');\n }\n\n const confirmReservationButton = page.getByRole('button', { name: 'Confirm Reservation' });\n await expect(confirmReservationButton).toBeVisible();\n await expect(confirmReservationButton).toHaveText('Confirm Reservation');\n}\n\nexport async function fillContactInfo(page: any, data: CheckoutTestData) {\n // Fill Contact Info fields\n await page.fill('input[name=\"userProfiles.0.Forename\"]', data.firstName);\n await page.fill('input[name=\"userProfiles.0.Surname\"]', data.lastName);\n await page.fill('input[name=\"userProfiles.0.Email\"]', data.email);\n await page.fill('input[name=\"userProfiles.0.Phone\"]', data.phone);\n await page.fill('input[name=\"userProfiles.0.Address\"]', data.address);\n await page.fill('input[name=\"userProfiles.0.City\"]', data.city);\n await page.fill('input[name=\"userProfiles.0.PostCode\"]', data.zipCode);\n\n // Country selector\n await page.fill('.rs-enhanced-country-field--search input', data.country);\n await page.waitForSelector('.rs-enhanced-country-field--suggest-list-country', { state: 'visible' });\n await page.click('.rs-enhanced-country-field--suggest-list-country-name'); // Assuming there's only one result...\n}\n\nexport async function fillPaymentInformation(page: any, data: CheckoutTestData) {\n // Fill Payment Information. Each of these fields is within their own secure iframe that we need to enter first.\n\n const cardNumberFrame = await (await page.waitForSelector('iframe[title=\"Secure card number input frame\"]')).contentFrame();\n if (cardNumberFrame) {\n await cardNumberFrame.fill('input[aria-label=\"Card Number Input\"]', data.cardNumber);\n }\n\n const cardExpiryFrame = await (await page.waitForSelector('iframe[title=\"Secure card expiration date input frame\"]')).contentFrame();\n if (cardExpiryFrame) {\n await cardExpiryFrame.fill('input[aria-label=\"Card expiration date\"]', data.cardExpiry);\n }\n\n const cardCvvFrame = await (await page.waitForSelector('iframe[title=\"Secure card security code input frame\"]')).contentFrame();\n if (cardCvvFrame) {\n await cardCvvFrame.fill('input[aria-label=\"Card Security Code\"]', data.cardCVC);\n }\n\n const cardNameFrame = await (await page.waitForSelector('iframe[title=\"Secure text input frame\"]')).contentFrame();\n if (cardNameFrame) {\n await cardNameFrame.fill('input[aria-label=\"Text input\"]', `${data.firstName} ${data.lastName}`);\n }\n}\n\nexport async function submitBookingReservation(page: any) {\n const confirmReservationButton = page.getByRole('button', { name: 'Confirm Reservation' });\n await expect(confirmReservationButton).toBeVisible();\n await confirmReservationButton.click();\n}\n\nexport async function verifyBookingConfirmation(page: any, data: CheckoutTestData) {\n // We should now be on the 'Thanks' step.\n await page.waitForSelector('.step-selector__item.is-current p.text-body:has-text(\"Thanks\")', { timeout: 5000 });\n\n // There should be a message thanking us for our booking.\n const thankYouMessage = page.locator('h3:has-text(\"Thank you\")');\n await expect(thankYouMessage).toBeVisible();\n\n // Click the 'More Details' button to show a modal with our booking details.\n const moreDetailsButton = page.getByRole('button', { name: 'More Details' });\n await expect(moreDetailsButton).toBeVisible();\n await moreDetailsButton.click();\n\n const modal = page.locator('div[class*=\"roomstay-modal\"]').first();\n await expect(modal).toBeVisible();\n\n // The modal should contain our name and email address.\n await expect(modal.locator(`text=${data.firstName} ${data.lastName}`)).toBeVisible();\n await expect(modal.locator(`text=${data.email}`)).toBeVisible();\n\n // Close the modal.\n const closeButton = modal.locator('i[name=\"Close\"]');\n await closeButton.click();\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
var _a, _b;
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
const test_1 = require("@playwright/test");
|
|
17
|
+
const set_1 = __importDefault(require("lodash/set"));
|
|
18
|
+
const args = process.argv.slice(3);
|
|
19
|
+
// Extracting CLI parameters
|
|
20
|
+
const version = args[0] || process.env.PLAYWRIGHT_VERSION;
|
|
21
|
+
const hotelId = args[1] || process.env.PLAYWRIGHT_HOTEL_ID;
|
|
22
|
+
const startDate = args[2] || process.env.PLAYWRIGHT_START_DATE;
|
|
23
|
+
const endDate = args[3] || process.env.PLAYWRIGHT_END_DATE;
|
|
24
|
+
const children = parseInt(args[4], 10) || parseInt(process.env.PLAYWRIGHT_CHILDREN || '0', 10);
|
|
25
|
+
const adults = parseInt(args[5], 10) || parseInt(process.env.PLAYWRIGHT_ADULTS || '1', 10);
|
|
26
|
+
const promocode = (_b = (_a = args[6]) !== null && _a !== void 0 ? _a : process.env.PLAYWRIGHT_PROMOCODE) !== null && _b !== void 0 ? _b : '';
|
|
27
|
+
// Set environment variables with PLAYWRIGHT_ prefix if parameters are provided
|
|
28
|
+
if (version) {
|
|
29
|
+
process.env.PLAYWRIGHT_VERSION = version;
|
|
30
|
+
}
|
|
31
|
+
if (hotelId) {
|
|
32
|
+
process.env.PLAYWRIGHT_HOTEL_ID = hotelId;
|
|
33
|
+
}
|
|
34
|
+
if (startDate) {
|
|
35
|
+
process.env.PLAYWRIGHT_START_DATE = startDate;
|
|
36
|
+
}
|
|
37
|
+
if (endDate) {
|
|
38
|
+
process.env.PLAYWRIGHT_END_DATE = endDate;
|
|
39
|
+
}
|
|
40
|
+
if (!isNaN(children)) {
|
|
41
|
+
process.env.PLAYWRIGHT_CHILDREN = children.toString();
|
|
42
|
+
}
|
|
43
|
+
if (!isNaN(adults)) {
|
|
44
|
+
process.env.PLAYWRIGHT_ADULTS = adults.toString();
|
|
45
|
+
}
|
|
46
|
+
if (promocode) {
|
|
47
|
+
process.env.PLAYWRIGHT_PROMOCODE = promocode;
|
|
48
|
+
}
|
|
49
|
+
if (!version || !hotelId || !startDate || !endDate || isNaN(children) || isNaN(adults)) {
|
|
50
|
+
console.error('Usage: yarn run test:playwright <version> <hotelId> <startDate> <endDate> <children> <adults> <promocode>');
|
|
51
|
+
console.error('Example: yarn run test:playwright 2/6.13 fa737326-ccdb-496a-bd31-2fbc3e97ed59 2024-08-28 2024-08-30 1 2');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
const url = `https://production.app.roomstay.io/${version}/feature.html?engine=fp&hotel=${hotelId}#/step/date/?arrive=${startDate}&depart=${endDate}&children=${children}&adults=${adults}&promocode=${promocode}`;
|
|
55
|
+
const params = { version, hotelId, startDate, endDate, children, adults, promocode, url };
|
|
56
|
+
console.log(params);
|
|
57
|
+
const spinnerSelector = '[name=Spinner]';
|
|
58
|
+
// function waitForAnimationEnd(locator: Locator) {
|
|
59
|
+
// return locator.evaluate((element) => Promise.all(element.getAnimations().map((animation) => animation.finished)));
|
|
60
|
+
// }
|
|
61
|
+
const getPrice = (text) => {
|
|
62
|
+
const textPrice = text === null || text === void 0 ? void 0 : text.replace(/[a-zA-Z$]/g, '');
|
|
63
|
+
return textPrice && !isNaN(+textPrice) ? +textPrice : undefined;
|
|
64
|
+
};
|
|
65
|
+
const TIMEOUT = 5 * 60 * 1000;
|
|
66
|
+
test_1.test.setTimeout(TIMEOUT);
|
|
67
|
+
(0, test_1.test)('price', ({ page }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
68
|
+
var _c;
|
|
69
|
+
//------------------------------------INIT------------------------------------
|
|
70
|
+
yield page.goto(url);
|
|
71
|
+
//------------------------------------WAIT HOTEL APPLIED------------------------------------
|
|
72
|
+
const [hotelResponse] = yield Promise.all([
|
|
73
|
+
page.waitForResponse((resp) => {
|
|
74
|
+
const url = new URL(resp.url()).pathname.replace(/\/$/, '').endsWith(`/frontend/hotel/${hotelId}`);
|
|
75
|
+
return url && resp.status() === 200;
|
|
76
|
+
}),
|
|
77
|
+
page.waitForResponse((resp) => {
|
|
78
|
+
const url = new URL(resp.url()).pathname.replace(/\/$/, '').endsWith(`/frontend/availability/${hotelId}`);
|
|
79
|
+
return url && resp.status() === 200;
|
|
80
|
+
}),
|
|
81
|
+
]);
|
|
82
|
+
const responseData = yield hotelResponse.json();
|
|
83
|
+
console.log({ responseData });
|
|
84
|
+
yield Promise.all([(0, test_1.expect)(page.locator('.hotel-name')).toHaveText(responseData.name), (0, test_1.expect)(page.locator('.room-details-container').first()).toBeVisible()]);
|
|
85
|
+
//------------------------------------INIT------------------------------------
|
|
86
|
+
const roomList = page.locator('.room-details-container');
|
|
87
|
+
//------------------------------------1. TOTAL RATE PRICE------------------------------------
|
|
88
|
+
let rooms = {};
|
|
89
|
+
for (let room of yield roomList.all()) {
|
|
90
|
+
yield room.locator(spinnerSelector).first().waitFor({
|
|
91
|
+
state: 'detached',
|
|
92
|
+
});
|
|
93
|
+
const roomName = yield room.locator('.room-details--content-title').textContent();
|
|
94
|
+
if (!roomName)
|
|
95
|
+
continue;
|
|
96
|
+
yield room
|
|
97
|
+
.getByRole('button', {
|
|
98
|
+
name: 'View rates now',
|
|
99
|
+
})
|
|
100
|
+
.click();
|
|
101
|
+
const cheapestPrice = getPrice(yield room.locator('.room-details--content-price .headline').first().textContent()) || 0;
|
|
102
|
+
(0, set_1.default)(rooms, [roomName, 'cheapestPrice'], cheapestPrice);
|
|
103
|
+
//------------------------------------1. CHEAPEST PRICE------------------------------------
|
|
104
|
+
const rates = room.locator('.room-info.--default');
|
|
105
|
+
for (let rate of yield rates.all()) {
|
|
106
|
+
yield rate.waitFor({
|
|
107
|
+
state: 'attached',
|
|
108
|
+
});
|
|
109
|
+
const rateName = yield rate.locator('.text-body').first().textContent();
|
|
110
|
+
// console.log(`${roomName}-${rateName}`);
|
|
111
|
+
if (!rateName)
|
|
112
|
+
continue;
|
|
113
|
+
// TODO wait for animation to end
|
|
114
|
+
yield page.waitForTimeout(1000);
|
|
115
|
+
// await waitForAnimationEnd(room.nth(2));
|
|
116
|
+
yield rate
|
|
117
|
+
.getByRole('button', {
|
|
118
|
+
name: 'View price breakdown',
|
|
119
|
+
})
|
|
120
|
+
.click();
|
|
121
|
+
const modal = page
|
|
122
|
+
.locator('[class^=SimpleModal]', {
|
|
123
|
+
hasText: '',
|
|
124
|
+
})
|
|
125
|
+
.first();
|
|
126
|
+
yield modal.waitFor({
|
|
127
|
+
state: 'attached',
|
|
128
|
+
});
|
|
129
|
+
const modalSpinners = modal.locator(spinnerSelector);
|
|
130
|
+
for (let spinner of yield modalSpinners.all()) {
|
|
131
|
+
yield spinner.waitFor({
|
|
132
|
+
state: 'detached',
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const nightlyPrices = modal.locator('.roomstay-tab-content .u-flex.u-marg-bottom');
|
|
136
|
+
const rateTotal = getPrice(yield modal.locator('.room-details--content-price-protection').textContent());
|
|
137
|
+
(0, set_1.default)(rooms, [roomName, 'rates', rateName, 'total'], rateTotal);
|
|
138
|
+
for (let night of yield nightlyPrices.all()) {
|
|
139
|
+
const nightName = yield night.locator('> *:nth-child(1)').textContent();
|
|
140
|
+
const nightPrice = getPrice(yield ((_c = night.locator('> *:nth-child(2)')) === null || _c === void 0 ? void 0 : _c.textContent()));
|
|
141
|
+
if (!nightName || !nightPrice)
|
|
142
|
+
continue;
|
|
143
|
+
// set(rooms[roomName].rates![rateName].nights![nightName] = {};
|
|
144
|
+
(0, set_1.default)(rooms, [roomName, 'rates', rateName, 'nights', nightName, 'price'], nightPrice);
|
|
145
|
+
}
|
|
146
|
+
yield modal.locator('[name=Close]').click();
|
|
147
|
+
yield modal.waitFor({
|
|
148
|
+
state: 'detached',
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
console.log(JSON.stringify(rooms, null, 2));
|
|
153
|
+
}));
|
|
154
|
+
//# sourceMappingURL=price.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"price.spec.js","sourceRoot":"/","sources":["e2e/price.spec.ts"],"names":[],"mappings":"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","sourcesContent":["import { test, expect } from '@playwright/test';\nimport set from 'lodash/set';\n\nconst args = process.argv.slice(3);\n// Extracting CLI parameters\nconst version = args[0] || process.env.PLAYWRIGHT_VERSION;\nconst hotelId = args[1] || process.env.PLAYWRIGHT_HOTEL_ID;\nconst startDate = args[2] || process.env.PLAYWRIGHT_START_DATE;\nconst endDate = args[3] || process.env.PLAYWRIGHT_END_DATE;\nconst children = parseInt(args[4], 10) || parseInt(process.env.PLAYWRIGHT_CHILDREN || '0', 10);\nconst adults = parseInt(args[5], 10) || parseInt(process.env.PLAYWRIGHT_ADULTS || '1', 10);\nconst promocode = args[6] ?? process.env.PLAYWRIGHT_PROMOCODE ?? '';\n\n// Set environment variables with PLAYWRIGHT_ prefix if parameters are provided\nif (version) {\n process.env.PLAYWRIGHT_VERSION = version;\n}\nif (hotelId) {\n process.env.PLAYWRIGHT_HOTEL_ID = hotelId;\n}\nif (startDate) {\n process.env.PLAYWRIGHT_START_DATE = startDate;\n}\nif (endDate) {\n process.env.PLAYWRIGHT_END_DATE = endDate;\n}\nif (!isNaN(children)) {\n process.env.PLAYWRIGHT_CHILDREN = children.toString();\n}\nif (!isNaN(adults)) {\n process.env.PLAYWRIGHT_ADULTS = adults.toString();\n}\nif (promocode) {\n process.env.PLAYWRIGHT_PROMOCODE = promocode;\n}\n\nif (!version || !hotelId || !startDate || !endDate || isNaN(children) || isNaN(adults)) {\n console.error('Usage: yarn run test:playwright <version> <hotelId> <startDate> <endDate> <children> <adults> <promocode>');\n console.error('Example: yarn run test:playwright 2/6.13 fa737326-ccdb-496a-bd31-2fbc3e97ed59 2024-08-28 2024-08-30 1 2');\n process.exit(1);\n}\n\nconst url = `https://production.app.roomstay.io/${version}/feature.html?engine=fp&hotel=${hotelId}#/step/date/?arrive=${startDate}&depart=${endDate}&children=${children}&adults=${adults}&promocode=${promocode}`;\nconst params: any = { version, hotelId, startDate, endDate, children, adults, promocode, url };\nconsole.log(params);\n\nconst spinnerSelector = '[name=Spinner]';\n\ntype Rooms = {\n [room: string]: {\n cheapestPrice?: number;\n rates?: {\n [rate: string]: {\n nights?: {\n [nightName: string]: {\n price?: number;\n };\n };\n total?: number;\n };\n };\n };\n};\n\n// function waitForAnimationEnd(locator: Locator) {\n// return locator.evaluate((element) => Promise.all(element.getAnimations().map((animation) => animation.finished)));\n// }\n\nconst getPrice = (text?: string | null): number | undefined => {\n const textPrice = text?.replace(/[a-zA-Z$]/g, '');\n return textPrice && !isNaN(+textPrice) ? +textPrice : undefined;\n};\n\nconst TIMEOUT = 5 * 60 * 1000;\ntest.setTimeout(TIMEOUT);\ntest('price', async ({ page }) => {\n //------------------------------------INIT------------------------------------\n await page.goto(url);\n\n //------------------------------------WAIT HOTEL APPLIED------------------------------------\n const [hotelResponse] = await Promise.all([\n page.waitForResponse((resp) => {\n const url = new URL(resp.url()).pathname.replace(/\\/$/, '').endsWith(`/frontend/hotel/${hotelId}`);\n return url && resp.status() === 200;\n }),\n page.waitForResponse((resp) => {\n const url = new URL(resp.url()).pathname.replace(/\\/$/, '').endsWith(`/frontend/availability/${hotelId}`);\n return url && resp.status() === 200;\n }),\n ]);\n const responseData = await hotelResponse.json();\n console.log({ responseData });\n await Promise.all([expect(page.locator('.hotel-name')).toHaveText(responseData.name), expect(page.locator('.room-details-container').first()).toBeVisible()]);\n\n //------------------------------------INIT------------------------------------\n const roomList = page.locator('.room-details-container');\n\n //------------------------------------1. TOTAL RATE PRICE------------------------------------\n let rooms: Rooms = {};\n for (let room of await roomList.all()) {\n await room.locator(spinnerSelector).first().waitFor({\n state: 'detached',\n });\n const roomName = await room.locator('.room-details--content-title').textContent();\n if (!roomName) continue;\n await room\n .getByRole('button', {\n name: 'View rates now',\n })\n .click();\n const cheapestPrice = getPrice(await room.locator('.room-details--content-price .headline').first().textContent()) || 0;\n set(rooms, [roomName, 'cheapestPrice'], cheapestPrice);\n //------------------------------------1. CHEAPEST PRICE------------------------------------\n const rates = room.locator('.room-info.--default');\n for (let rate of await rates.all()) {\n await rate.waitFor({\n state: 'attached',\n });\n const rateName = await rate.locator('.text-body').first().textContent();\n // console.log(`${roomName}-${rateName}`);\n if (!rateName) continue;\n // TODO wait for animation to end\n await page.waitForTimeout(1000);\n // await waitForAnimationEnd(room.nth(2));\n await rate\n .getByRole('button', {\n name: 'View price breakdown',\n })\n .click();\n const modal = page\n .locator('[class^=SimpleModal]', {\n hasText: '',\n })\n .first();\n await modal.waitFor({\n state: 'attached',\n });\n const modalSpinners = modal.locator(spinnerSelector);\n for (let spinner of await modalSpinners.all()) {\n await spinner.waitFor({\n state: 'detached',\n });\n }\n const nightlyPrices = modal.locator('.roomstay-tab-content .u-flex.u-marg-bottom');\n const rateTotal = getPrice(await modal.locator('.room-details--content-price-protection').textContent());\n set(rooms, [roomName, 'rates', rateName, 'total'], rateTotal);\n for (let night of await nightlyPrices.all()) {\n const nightName = await night.locator('> *:nth-child(1)').textContent();\n const nightPrice = getPrice(await night.locator('> *:nth-child(2)')?.textContent());\n if (!nightName || !nightPrice) continue;\n // set(rooms[roomName].rates![rateName].nights![nightName] = {};\n set(rooms, [roomName, 'rates', rateName, 'nights', nightName, 'price'], nightPrice);\n }\n await modal.locator('[name=Close]').click();\n await modal.waitFor({\n state: 'detached',\n });\n }\n }\n\n console.log(JSON.stringify(rooms, null, 2));\n});\n"]}
|