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:
@@ -1,75 +1,88 @@
|
||||
import { test, expect } from './fixtures';
|
||||
import { test, expect, uniqueSuffix, publishItem } from './fixtures';
|
||||
|
||||
const suffix = () => `s${Date.now().toString(36)}${Math.random().toString(36).slice(2, 7)}`;
|
||||
|
||||
// Creates an item and leaves it as it arrives — pending. Deliberately does not
|
||||
// publish, unlike the other specs' fixtures, because the staged state is what
|
||||
// is being tested here.
|
||||
async function createStagedItem(page: import('@playwright/test').Page, name: string) {
|
||||
const created = await page.request.post('/api/admin/items', {
|
||||
/**
|
||||
* Creates an item and leaves it as it arrives — pending.
|
||||
*
|
||||
* Deliberately does not publish, unlike the shared `createItem` helper, because
|
||||
* the staged state is the thing under test here. The status assertion is the
|
||||
* whole premise: creating something does not publish it.
|
||||
*/
|
||||
async function createStagedItem(
|
||||
request: import('@playwright/test').APIRequestContext,
|
||||
name: string
|
||||
): Promise<number> {
|
||||
const created = await request.post('/api/admin/items', {
|
||||
multipart: { name, description: '', price: '55', category_id: '', tags: '[]' }
|
||||
});
|
||||
expect(created.ok()).toBeTruthy();
|
||||
const body = await created.json();
|
||||
// The whole premise: creating something does not publish it.
|
||||
expect(body.status).toBe('pending');
|
||||
return body.id as number;
|
||||
}
|
||||
|
||||
const rowFor = (page: import('@playwright/test').Page, name: string) =>
|
||||
page.getByRole('row').filter({ hasText: name });
|
||||
|
||||
test.describe('Staging an item until it is published', () => {
|
||||
test('a new item is held back from the storefront until published', async ({ page }) => {
|
||||
const name = `Staged ${suffix()}`;
|
||||
await createStagedItem(page, name);
|
||||
test('a new item is held back from the storefront until published', async ({
|
||||
page,
|
||||
storefront,
|
||||
admin,
|
||||
adminInventory
|
||||
}) => {
|
||||
const name = `Staged ${uniqueSuffix()}`;
|
||||
await createStagedItem(page.request, name);
|
||||
|
||||
// Not in the catalogue while pending.
|
||||
await page.goto('/');
|
||||
await storefront.goto();
|
||||
await expect(page.getByText(name)).toHaveCount(0);
|
||||
|
||||
// It is in the admin, marked as pending.
|
||||
await page.goto('/admin');
|
||||
const row = rowFor(page, name);
|
||||
await expect(row).toBeVisible();
|
||||
await expect(row.getByText('PENDING')).toBeVisible();
|
||||
await admin.goto();
|
||||
await expect(adminInventory.row(name)).toBeVisible();
|
||||
await expect(adminInventory.status(name, 'PENDING')).toBeVisible();
|
||||
|
||||
await row.getByRole('button', { name: 'Publish' }).click();
|
||||
await expect(row.getByText('AVAILABLE')).toBeVisible();
|
||||
await adminInventory.publishButton(name).click();
|
||||
await expect(adminInventory.status(name, 'AVAILABLE')).toBeVisible();
|
||||
|
||||
// And now a customer can see it.
|
||||
await page.goto('/');
|
||||
await storefront.goto();
|
||||
await expect(page.getByText(name)).toBeVisible();
|
||||
});
|
||||
|
||||
test('publishing can be undone while nobody is holding the item', async ({ page }) => {
|
||||
const name = `Staged ${suffix()}`;
|
||||
const id = await createStagedItem(page, name);
|
||||
expect((await page.request.post(`/api/admin/items/${id}/mark-available`)).ok()).toBeTruthy();
|
||||
test('publishing can be undone while nobody is holding the item', async ({
|
||||
page,
|
||||
storefront,
|
||||
admin,
|
||||
adminInventory
|
||||
}) => {
|
||||
const name = `Staged ${uniqueSuffix()}`;
|
||||
const id = await createStagedItem(page.request, name);
|
||||
await publishItem(page.request, id);
|
||||
|
||||
await page.goto('/');
|
||||
await storefront.goto();
|
||||
await expect(page.getByText(name)).toBeVisible();
|
||||
|
||||
await page.goto('/admin');
|
||||
const row = rowFor(page, name);
|
||||
await row.getByRole('button', { name: 'Unpublish' }).click();
|
||||
await expect(row.getByText('PENDING')).toBeVisible();
|
||||
await admin.goto();
|
||||
await adminInventory.unpublishButton(name).click();
|
||||
await expect(adminInventory.status(name, 'PENDING')).toBeVisible();
|
||||
|
||||
await page.goto('/');
|
||||
await storefront.goto();
|
||||
await expect(page.getByText(name)).toHaveCount(0);
|
||||
});
|
||||
|
||||
// The two features together, which is the reason for wanting both: a staged
|
||||
// item is previewed as it will look once live, because a customer never sees
|
||||
// the pending state and "how will this look" is the question being asked.
|
||||
test('a pending item previews as it will look once published', async ({ page }) => {
|
||||
const name = `Staged ${suffix()}`;
|
||||
await createStagedItem(page, name);
|
||||
test('a pending item previews as it will look once published', async ({
|
||||
page,
|
||||
admin,
|
||||
adminInventory
|
||||
}) => {
|
||||
const name = `Staged ${uniqueSuffix()}`;
|
||||
await createStagedItem(page.request, name);
|
||||
|
||||
await page.goto('/admin');
|
||||
await page.getByRole('button', { name }).click();
|
||||
await admin.goto();
|
||||
await adminInventory.openPreview(name);
|
||||
|
||||
const drawer = page.getByRole('dialog', { name: `Preview: ${name}` });
|
||||
const drawer = adminInventory.previewDrawer(name);
|
||||
await expect(drawer).toBeVisible();
|
||||
await expect(drawer.getByText('$55.00')).toBeVisible();
|
||||
// The live card's action, not a pending placeholder.
|
||||
|
||||
Reference in New Issue
Block a user