test(e2e): convert the favorites, availability and orders specs (#137)
favorites, favorites-filter, sold-filter, orders and pending-publish. Five more copies of "register a customer" and three more of the hardcoded base URL go with them. Two locators that were hiding real knowledge are now named. `gridCell` is the item's whole antd column rather than its card, needed because the SOLD ribbon renders outside the card — three specs reached for `.ant-col` directly to get at it. And `chooseAvailability` goes through the title attribute because antd's Segmented hides the real radio behind a styled label, so the input is found by role and cannot be clicked; that fact was written out twice in comments and is now written once in code. The favorite control is located page-wide rather than within a card. The storefront paginates as items accumulate and the control is named for its item anyway, so scoping to a card bought nothing and broke whenever the card was on another page. favorites-filter keeps its local `favorite()` helper. It is genuinely local — decline the opt-in, wait for the fading modal to stop intercepting pointer events, confirm the heart flipped — and belongs to that file's subject rather than to the storefront. It now takes page objects as parameters instead of reaching for locators itself, which is what a spec-level helper should look like. Two specs still drive the registration form rather than taking the `customer` fixture, and deliberately. Both are about a signed-out visitor being interrupted mid-action — favoriting an item, or switching on the favorites filter — and the claim is that the thing they asked for survives the interruption. Replacing the interruption with an API call would delete the test. Verified: favorites 6/6, favorites-filter 7/7, sold-filter 6/6, orders and pending-publish 8/8. Notably favorites-filter's "keeps showing a favorite after it sells" passes, which had been failing on a strict-mode violation from two items sharing a name across runs. Refs #137
This commit is contained in:
@@ -20,6 +20,10 @@ export class StorefrontPage {
|
||||
* all" of its own, so an unscoped one matches both.
|
||||
*/
|
||||
readonly activeFilters: Locator;
|
||||
/** Shown instead of the grid when filters match nothing. */
|
||||
readonly noMatchesNotice: Locator;
|
||||
/** Shown instead of that when a favorites filter is set with no session. */
|
||||
readonly favoritesNeedSignInNotice: Locator;
|
||||
|
||||
/**
|
||||
* The three things the catalogue can say instead of listing items. Named
|
||||
@@ -40,6 +44,8 @@ export class StorefrontPage {
|
||||
this.filtersButton = page.getByRole('button', { name: /Filters/ });
|
||||
this.privacyPolicyLink = page.getByRole('link', { name: 'Privacy Policy' });
|
||||
this.activeFilters = page.getByRole('group', { name: 'Active filters' });
|
||||
this.noMatchesNotice = page.getByText('No items match these filters');
|
||||
this.favoritesNeedSignInNotice = page.getByText('Sign in to see the items you have favorited');
|
||||
|
||||
this.emptyNotice = page.getByText('No items yet');
|
||||
this.loadFailureNotice = page.getByText("Couldn't load items");
|
||||
@@ -75,15 +81,56 @@ export class StorefrontPage {
|
||||
return this.card(name).getByRole('button', { name: 'Add to Cart' });
|
||||
}
|
||||
|
||||
/** The heart. Named for what it does rather than what it looks like. */
|
||||
/**
|
||||
* The heart, in whichever state it is currently in.
|
||||
*
|
||||
* Located page-wide rather than inside the card: the storefront paginates as
|
||||
* items accumulate, and the control is named for the item anyway, so scoping
|
||||
* to a card buys nothing and breaks when the card is on another page.
|
||||
*/
|
||||
favoriteToggle(name: string): Locator {
|
||||
return this.card(name).getByRole('button', { name: /favorite/i });
|
||||
return this.page.getByRole('button', { name: new RegExp(`(Add|Remove) ${name}`) });
|
||||
}
|
||||
|
||||
/**
|
||||
* The two settled states, named separately because the tests assert on the
|
||||
* transition between them — a favorite that took is a "Remove" control, and
|
||||
* one that did not is still an "Add".
|
||||
*/
|
||||
addToFavoritesButton(name: string): Locator {
|
||||
return this.page.getByRole('button', { name: `Add ${name} to favorites` });
|
||||
}
|
||||
|
||||
removeFromFavoritesButton(name: string): Locator {
|
||||
return this.page.getByRole('button', { name: `Remove ${name} from favorites` });
|
||||
}
|
||||
|
||||
async openFilters(): Promise<void> {
|
||||
await this.filtersButton.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* The item's whole grid cell rather than its card.
|
||||
*
|
||||
* The SOLD ribbon is rendered outside the card, so a test asserting on it has
|
||||
* to reach the cell — and it has to be scoped to this item, because sold items
|
||||
* from earlier runs share the page.
|
||||
*/
|
||||
gridCell(name: string): Locator {
|
||||
return this.page.locator('.ant-col').filter({ hasText: name });
|
||||
}
|
||||
|
||||
/**
|
||||
* The availability segment.
|
||||
*
|
||||
* antd's Segmented hides the real radio behind a styled label, so the input is
|
||||
* found by role but cannot be clicked. The label carries a title attribute,
|
||||
* which is the same handle this suite uses for antd Select options.
|
||||
*/
|
||||
async chooseAvailability(label: string): Promise<void> {
|
||||
await this.page.getByTitle(label, { exact: true }).click();
|
||||
}
|
||||
|
||||
removeFilterChip(name: string): Locator {
|
||||
return this.page.getByRole('button', { name: `Remove filter ${name}` });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user