Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/nextjs/components/CartCheckoutButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export function CartCheckoutButton() {
<ShopPayButton width="100%" borderRadius="8px" />
<a
href={checkoutUrl}
data-testid="cart-checkout"
className="rounded-button button-primary focus-visible:outline-accent inline-flex h-11 w-full items-center justify-center px-4 text-sm font-medium no-underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2"
>
{content.cart.checkout}
Expand Down
1 change: 1 addition & 0 deletions examples/nextjs/components/CartDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ function CartDrawerInner() {
<ShopPayButton width="100%" borderRadius="8px" />
<a
href={checkoutUrl}
data-testid="cart-checkout"
className="rounded-button button-primary focus-visible:outline-accent inline-flex h-11 w-full items-center justify-center gap-2 px-4 text-sm font-medium no-underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2"
>
{content.cart.checkout}
Expand Down
1 change: 1 addition & 0 deletions examples/nuxt/components/CartTotals.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const isTotalsPending = useCart((s) => s.pending.cost === true || s.revalidating
<ShopPayButton width="100%" border-radius="4px" />
<a
:href="checkoutUrl"
data-testid="cart-checkout"
class="block rounded bg-black px-6 py-3 text-center text-sm font-medium text-white hover:bg-neutral-800"
>
Check out
Expand Down
2 changes: 1 addition & 1 deletion packages/storefront-e2e/docs/storefront-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Cart UI that has no ID in Test IDs is found by role, label, and text.

The checkout test verifies only the storefront-owned side of the checkout handoff.

It verifies the added product and variant in the storefront cart, clicks the visible checkout link, and waits for a checkout URL. It does not depend on the external checkout document loading, enter customer data, payment data, or perform any irreversible checkout step.
It verifies the added product and variant in the storefront cart, clicks the first visible `cart-checkout` link on the cart page, and waits for a checkout URL. It finds the added line with `cart-drawer`, `cart-page`, and `cart-line`. It does not depend on the external checkout document loading, enter customer data, payment data, or perform any irreversible checkout step.

If the storefront does not start checkout navigation, the test fails as a contract issue. External checkout availability is outside this suite's boundary.

Expand Down
19 changes: 13 additions & 6 deletions packages/storefront-e2e/specs/checkout/checkout.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { test, type CheckoutTestProduct } from "./config";
const CART_SETTLE_TIMEOUT_MS = 15_000;
// Any non-empty accessible name, so other and localized labels pass but an unlabeled icon button fails.
const ADD_TO_CART_CONTROL_NAME = /\S/;
const CHECKOUT_CONTROL_NAME = /check\s*out|checkout|continue to checkout/i;

type CartExpectation = {
readonly cartPath: string;
Expand Down Expand Up @@ -78,7 +77,11 @@ async function startCheckoutFromPopulatedCart(
): Promise<void> {
await expectCartLine(page, expectation);

const checkout = page.getByRole("link", { name: CHECKOUT_CONTROL_NAME }).first();
const checkout = page
.getByTestId("cart-page")
.getByTestId("cart-checkout")
.filter({ visible: true })
.first();
await expect(checkout).toBeVisible();
const checkoutUrl = await requireCheckoutUrl(page, checkout, expectation.cartPath);
const isCheckoutNavigation = (url: URL) => url.href === checkoutUrl.href;
Expand All @@ -101,9 +104,9 @@ async function startCheckoutFromPopulatedCart(

async function expectCartLine(page: Page, expectation: CartExpectation): Promise<Locator> {
const line = page
.getByRole("main")
.getByRole("listitem")
.filter({ hasText: expectation.productTitle })
.getByTestId("cart-page")
.getByTestId("cart-line")
.filter({ hasText: expectation.productTitle, visible: true })
.first();
await expect(line).toBeVisible({ timeout: CART_SETTLE_TIMEOUT_MS });

Expand All @@ -115,7 +118,11 @@ async function expectCartLine(page: Page, expectation: CartExpectation): Promise
}

function cartOverlayLineFor(page: Page, productTitle: string): Locator {
return page.getByRole("dialog").getByRole("listitem").filter({ hasText: productTitle }).first();
return page
.getByTestId("cart-drawer")
.getByTestId("cart-line")
.filter({ hasText: productTitle, visible: true })
.first();
}

function shouldAssertVariant(variantLabel: string): boolean {
Expand Down
3 changes: 2 additions & 1 deletion skills/hydrogen-storefront-testing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ The product title needs no test ID. The suite requires it to be the page's `<h1>
| `cart-line-increase` | The increase quantity button of a cart line |
| `cart-line-decrease` | The decrease quantity button of a cart line |
| `cart-line-remove` | The remove button of a cart line |
| `cart-checkout` | The checkout link of the cart page |

The suite finds a cart line by its product title inside the visible `cart-page` or the visible `cart-drawer`. After Add to cart, it clicks `cart-trigger` only when it needs the drawer and the added line is not visible. It finds the quantity input and the line buttons inside that line, and fails if the buttons do not have the button role, such as a native `<button>`.
The suite finds a cart line by its product title inside the visible `cart-page` or the visible `cart-drawer`. After Add to cart, it clicks `cart-trigger` only when it needs the drawer and the added line is not visible. It finds the quantity input and the line buttons inside that line, and fails if the buttons do not have the button role, such as a native `<button>`. The checkout test reuses `cart-drawer`, `cart-page`, and `cart-line` to find the added line. Then it clicks the first visible `cart-checkout` on the cart page and expects a checkout URL.

## How to adopt

Expand Down
1 change: 1 addition & 0 deletions templates/react-router/app/components/CartDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ function CartFooter() {
<ShopPayButton width="100%" borderRadius="8px" />
<a
href={cart.checkoutUrl}
data-testid="cart-checkout"
className="rounded-button button-primary focus-visible:outline-accent inline-flex h-11 w-full items-center justify-center gap-2 px-4 text-sm font-medium no-underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 motion-safe:transition-[color,background-color,border-color,transform] motion-safe:active:scale-[0.97]"
>
Checkout
Expand Down
1 change: 1 addition & 0 deletions templates/react-router/app/routes/cart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export default function CartRoute() {
<ShopPayButton width="100%" borderRadius="8px" />
<a
href={cart.checkoutUrl}
data-testid="cart-checkout"
className="rounded-button button-primary focus-visible:outline-accent inline-flex h-11 w-full items-center justify-center gap-2 px-4 text-sm font-medium no-underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 motion-safe:transition-[color,background-color,border-color,transform] motion-safe:active:scale-[0.97]"
>
Checkout
Expand Down
Loading