Skip to content

Commit 53ee489

Browse files
committed
Add RN hardcoded buyer identity E2E smoke
1 parent b7b3c9d commit 53ee489

14 files changed

Lines changed: 154 additions & 59 deletions

File tree

e2e/README.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
This directory contains Maestro end-to-end smoke flows for Checkout Kit sample
44
apps.
55

6-
The current runnable suite starts with React Native. It verifies a full guest
7-
checkout from a seeded cart in the sample app, through Shopify checkout, and
8-
back to the app after completion.
6+
The current runnable suite starts with React Native. It verifies full guest and
7+
hardcoded buyer identity checkouts from seeded carts, through Shopify checkout,
8+
and back to the app after completion.
99

1010
## Run locally
1111

@@ -28,20 +28,24 @@ dev rn e2e android
2828

2929
The React Native commands start Metro if needed, build and launch the target
3030
sample app, then run Maestro. They require the standard storefront `.env` setup,
31-
but the E2E flow seeds its own cart through the bootstrap deep link; no manual
32-
sample cart setup is required.
31+
but the E2E flows seed their own carts through the bootstrap deep link. The
32+
React Native bootstrap link accepts `buyerIdentityMode`, so guest and hardcoded
33+
buyer identity scenarios share the same cart setup path. No manual sample cart
34+
setup is required.
3335

3436
## Files
3537

3638
- `config.yaml` configures Maestro for shared platform behavior.
3739
- `flows/` contains reusable Maestro subflows for app setup and checkout steps.
38-
- `tests/react-native/full-guest-checkout.yaml` composes the React Native guest
40+
- `tests/react-native/checkout-guest.yaml` composes the React Native guest
3941
checkout smoke test from those subflows.
42+
- `tests/react-native/checkout-hardcoded-buyer-identity.yaml` verifies checkout
43+
from a bootstrapped cart with hardcoded buyer identity.
4044

4145
## Scope
4246

4347
This smoke flow is intended to catch regressions in the React Native sample app
44-
integration surface: cart bootstrap, checkout presentation, checkout
45-
completion, and return to the sample app. It is not a replacement for
46-
checkout-web's browser-based coverage or for future native Swift and Android
47-
sample-app E2E coverage.
48+
integration surface: cart bootstrap, buyer identity configuration, checkout
49+
presentation, checkout completion, and return to the sample app. It is not a
50+
replacement for checkout-web's browser-based coverage or for future native
51+
Swift and Android sample-app E2E coverage.

e2e/config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
flows:
2-
- tests/**/*.yaml
2+
- tests/react-native/*.yaml
33

44
platform:
55
ios:

e2e/flows/app/bootstrap-cart-from-link.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ appId: ${APP_ID}
99
# Reset app data before testing the deep-link cold-start path. The app is
1010
# stopped so openLink is the launch that seeds the cart.
1111
- stopApp
12-
- openLink: "${CART_BOOTSTRAP_LINK}"
12+
- openLink: "${CART_BOOTSTRAP_BASE_LINK}&buyerIdentityMode=${BUYER_IDENTITY_MODE}"
1313
- waitForAnimationToEnd
14+
- runFlow: dismiss-dev-warning.yaml
1415
- extendedWaitUntil:
1516
visible:
1617
id: checkout-button
17-
timeout: 30000
18+
timeout: 10000
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
appId: ${APP_ID}
2+
---
3+
- runFlow:
4+
when:
5+
visible: "Open debugger to view warnings."
6+
commands:
7+
- tapOn:
8+
point: "92%,92%"
9+
- waitForAnimationToEnd

e2e/flows/app/launch.yaml

Lines changed: 0 additions & 12 deletions
This file was deleted.

e2e/flows/checkout/present.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ appId: ${APP_ID}
55
enabled: true
66
- extendedWaitUntil:
77
visible:
8-
text: "^Email( or mobile phone number)?$"
8+
text: "^(Email( or mobile phone number)?|Delivery|Card number)$"
99
timeout: 10000

e2e/tests/react-native/full-guest-checkout.yaml renamed to e2e/tests/react-native/checkout-guest.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
appId: ${APP_ID}
2-
name: React Native full guest checkout
2+
name: React Native checkout - guest
33

44
env:
5+
# Sample app buyer identity configuration
6+
BUYER_IDENTITY_MODE: "guest"
7+
58
# Checkout contact fixture
69
EMAIL: "maestro.e2e@shopify.com"
710
FIRST_NAME: "Maestro"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
appId: ${APP_ID}
2+
name: React Native checkout - hardcoded buyer identity
3+
4+
env:
5+
# Sample app buyer identity configuration
6+
BUYER_IDENTITY_MODE: "hardcoded"
7+
8+
# Checkout payment fixture
9+
CARD_NUMBER: "1"
10+
CARD_SECURITY_CODE: "123"
11+
12+
# Accepted successful checkout states for this smoke test.
13+
POST_SUBMIT_RESULT_PATTERN: ".*(Thank you|Your order|Order confirmed|confirmation).*"
14+
---
15+
- runFlow: ../../flows/app/bootstrap-cart-from-link.yaml
16+
- runFlow: ../../flows/checkout/present.yaml
17+
- runFlow: ../../flows/checkout/fill-payment-card.yaml
18+
- runFlow: ../../flows/checkout/submit.yaml
19+
- runFlow: ../../flows/checkout/assert-complete.yaml
20+
- runFlow: ../../flows/checkout/assert-returned-to-empty-cart.yaml

platforms/react-native/sample/src/context/Cart.tsx

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,19 @@ interface Context {
2222
addingToCart: Set<string>;
2323
clearCart: () => void;
2424
addToCart: (variantId: string, quantity?: number) => Promise<void>;
25-
seedCart: (variantId: string, quantity?: number) => Promise<void>;
25+
seedCart: (
26+
variantId: string,
27+
quantity?: number,
28+
buyerIdentityMode?: BuyerIdentityMode,
29+
) => Promise<void>;
2630
removeFromCart: (variantId: string) => Promise<void>;
2731
}
2832

33+
type AddToCartOptions = {
34+
forceNewCart?: boolean;
35+
buyerIdentityMode?: BuyerIdentityMode;
36+
};
37+
2938
const defaultCartId = undefined;
3039
const defaultCheckoutURL = undefined;
3140
const defaultTotalQuantity = 0;
@@ -114,19 +123,30 @@ export const CartProvider: React.FC<PropsWithChildren> = ({children}) => {
114123
}, [cartId, fetchCart, setTotalQuantity]);
115124

116125
const addToCart = useCallback(
117-
async (variantId: string, quantity = 1, forceNewCart = false) => {
126+
async (
127+
variantId: string,
128+
quantity = 1,
129+
{
130+
forceNewCart = false,
131+
buyerIdentityMode = appConfig.buyerIdentityMode,
132+
}: AddToCartOptions = {},
133+
) => {
118134
if (!Number.isInteger(quantity) || quantity < 1) {
119135
throw new Error('Cart quantity must be a positive integer');
120136
}
121137

122138
let id = forceNewCart ? undefined : cartId;
139+
const cartAppConfig = {
140+
...appConfig,
141+
buyerIdentityMode,
142+
};
123143

124144
dispatch({type: 'add', variantId});
125145

126146
try {
127147
if (
128148
!id &&
129-
appConfig.buyerIdentityMode === BuyerIdentityMode.CustomerAccount &&
149+
buyerIdentityMode === BuyerIdentityMode.CustomerAccount &&
130150
!isAuthenticated
131151
) {
132152
const signInRequiredMessage =
@@ -144,14 +164,11 @@ export const CartProvider: React.FC<PropsWithChildren> = ({children}) => {
144164

145165
if (!id) {
146166
let customerAccessToken: string | undefined;
147-
if (
148-
appConfig.buyerIdentityMode === BuyerIdentityMode.CustomerAccount
149-
) {
150-
customerAccessToken =
151-
(await getValidAccessToken()) ?? undefined;
167+
if (buyerIdentityMode === BuyerIdentityMode.CustomerAccount) {
168+
customerAccessToken = (await getValidAccessToken()) ?? undefined;
152169
}
153170
const cartInput = createBuyerIdentityCartInput(
154-
appConfig,
171+
cartAppConfig,
155172
customerAccessToken,
156173
);
157174
const cart = await createCart({variables: {input: cartInput}});
@@ -198,8 +215,15 @@ export const CartProvider: React.FC<PropsWithChildren> = ({children}) => {
198215
);
199216

200217
const seedCart = useCallback(
201-
async (variantId: string, quantity = 1) => {
202-
await addToCart(variantId, quantity, true);
218+
async (
219+
variantId: string,
220+
quantity = 1,
221+
buyerIdentityMode?: BuyerIdentityMode,
222+
) => {
223+
await addToCart(variantId, quantity, {
224+
forceNewCart: true,
225+
buyerIdentityMode,
226+
});
203227
},
204228
[addToCart],
205229
);

platforms/react-native/sample/src/hooks/useE2ECartBootstrap.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@ function errorMessage(error: unknown) {
1515
return error instanceof Error ? error.message : 'Unknown error';
1616
}
1717

18-
export function useE2ECartBootstrap({
19-
onCartReady,
20-
}: UseE2ECartBootstrapOptions) {
18+
export function useE2ECartBootstrap({onCartReady}: UseE2ECartBootstrapOptions) {
2119
const {seedCart} = useCart();
2220
const {queries} = useShopify();
2321
const [fetchProducts] = queries.products;
@@ -52,7 +50,11 @@ export function useE2ECartBootstrap({
5250
throw new Error('Cart bootstrap product variant was not found');
5351
}
5452

55-
await seedCart(variantId, cartBootstrapLink.quantity);
53+
await seedCart(
54+
variantId,
55+
cartBootstrapLink.quantity,
56+
cartBootstrapLink.buyerIdentityMode,
57+
);
5658
onCartReady();
5759
} catch (error) {
5860
Alert.alert('Cart bootstrap failed', errorMessage(error));

0 commit comments

Comments
 (0)