-
Notifications
You must be signed in to change notification settings - Fork 190
✅ [RUM-17233] Add Shopify checkout views and actions e2e test #4941
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d160743
fd9496c
7c81df8
ef6cdb5
eb67446
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,7 @@ export interface SetupOptions { | |
| callerLocation?: CallerLocation | ||
| mockClock: boolean | ||
| salesforceApp: SalesforceApp | undefined | ||
| shopifyApp: boolean | ||
| } | ||
|
|
||
| export interface CallerLocation { | ||
|
|
@@ -304,22 +305,40 @@ export function microfrontendSetup(options: SetupOptions, servers: Servers) { | |
| }) | ||
| } | ||
|
|
||
| // Salesforce apps don't serve a locally-generated page body; this factory only drives the | ||
| // page-side setup needed to init RUM on the remote Salesforce page. | ||
| // Salesforce and Shopify apps don't serve a locally-generated page body; this factory only | ||
| // intercepts the remote bundle request and injects the RUM configuration read by the app's | ||
| // own init snippet/component, via globals set on every new document | ||
| async function interceptRemoteBundleAndConfigureRum( | ||
| page: Page, | ||
| urlPattern: RegExp, | ||
| resolveFilePath: (url: string) => string, | ||
| options: SetupOptions, | ||
| servers: Servers, | ||
| headers?: Record<string, string> | ||
| ): Promise<void> { | ||
| await page.route(urlPattern, async (route) => { | ||
| await route.fulfill({ | ||
| body: await readFile(resolveFilePath(route.request().url())), | ||
| contentType: 'application/javascript', | ||
| headers, | ||
| }) | ||
| }) | ||
|
|
||
| if (options.rum) { | ||
| await page.addInitScript( | ||
| `window.RUM_CONFIGURATION = ${formatConfiguration(options.rum, servers)} | ||
| window.RUM_CONTEXT = ${JSON.stringify(options.context)}` | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| export async function salesforceSetup(options: SetupOptions, servers: Servers, page: Page): Promise<string> { | ||
| const salesforceAppDirectory = options.salesforceApp === 'experience-cloud' ? 'sf-experience-app' : 'sf-lwc-app' | ||
| const salesforceBundlePath = resolve( | ||
| __dirname, | ||
| `../../../apps/${salesforceAppDirectory}/force-app/main/default/staticresources/datadog_rum_salesforce.js` | ||
| ) | ||
|
|
||
| await page.route(/\/resource(?:\/[^/?#]+)?\/datadog_rum_salesforce(?:\.js)?(?:[/?#].*)?$/, async (route) => { | ||
| await route.fulfill({ | ||
| body: await readFile(salesforceBundlePath), | ||
| contentType: 'application/javascript', | ||
| }) | ||
| }) | ||
|
|
||
| if (options.salesforceApp === 'lwc') { | ||
| const { accessToken, instanceUrl } = await getSalesforceLwcSession() | ||
| await page.context().addCookies([ | ||
|
|
@@ -331,15 +350,41 @@ export async function salesforceSetup(options: SetupOptions, servers: Servers, p | |
| ]) | ||
| } | ||
|
|
||
| if (options.rum) { | ||
| // Both sf-lwc-app and sf-experience-app have a committed datadogInit LWC that reads | ||
| // these globals and calls DD_RUM.init. On experience-cloud, that component only runs | ||
| // when the page is loaded with init=true. | ||
| await page.addInitScript( | ||
| `window.RUM_CONFIGURATION = ${formatConfiguration(options.rum, servers)} | ||
| window.RUM_CONTEXT = ${JSON.stringify(options.context)}` | ||
| ) | ||
| } | ||
| // Both sf-lwc-app and sf-experience-app have a committed datadogInit LWC that reads the | ||
| // injected globals and calls DD_RUM.init. On experience-cloud, that component only runs | ||
| // when the page is loaded with init=true. | ||
| await interceptRemoteBundleAndConfigureRum( | ||
| page, | ||
| /\/resource(?:\/[^/?#]+)?\/datadog_rum_salesforce(?:\.js)?(?:[/?#].*)?$/, | ||
| () => salesforceBundlePath, | ||
| options, | ||
| servers | ||
| ) | ||
| return '' | ||
| } | ||
|
|
||
| // Matches the CDN URL used by the store's Theme Liquid snippet and Custom Pixel for the main | ||
| // bundle and its dynamically-imported chunks (e.g. the session replay recorder), served from | ||
| // `https://www.datadoghq-browser-agent.com/<site>/v<major>/[chunks/]<name->]datadog-rum-shopify.js` | ||
| const SHOPIFY_ASSET_URL_PATTERN = | ||
| /datadoghq-browser-agent\.com\/[^/]+\/v\d+\/(chunks\/)?([\w-]*datadog-rum-shopify\.js)(?:[?#].*)?$/ | ||
|
|
||
| export async function shopifySetup(options: SetupOptions, servers: Servers, page: Page): Promise<string> { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: those setup functions are templates to render an HTML page the e2e test will load. It seems like you are doing very different things here. What about moving this logic in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was following the example of the Salesforce, which has similar setup pattern
Actually not that different - the part of rendering an HTML page for e2e test is injecting the local sdk version and passing the init options for it - and this is exactly what I'm doing here. This method doesn't render the html - it's rendered by predefined Shopify dev-store - but I still need to override the sdk bundle path and pass the init config for it. Therefore I believe it's the right place to do this, it would look overly cumbersome in |
||
| const shopifyBundleDir = resolve(__dirname, '../../../../packages/browser-rum-shopify/bundle') | ||
|
|
||
| await interceptRemoteBundleAndConfigureRum( | ||
| page, | ||
| SHOPIFY_ASSET_URL_PATTERN, | ||
| (url) => { | ||
| const [, chunksSegment, fileName] = SHOPIFY_ASSET_URL_PATTERN.exec(url) || [] | ||
| return resolve(shopifyBundleDir, chunksSegment || '', fileName) | ||
| }, | ||
| options, | ||
| servers, | ||
| // The snippets load the script with `crossOrigin = 'anonymous'`, so the browser enforces | ||
| // CORS on this response even though it never leaves the machine. | ||
| { 'access-control-allow-origin': '*' } | ||
| ) | ||
| return '' | ||
| } | ||
|
|
||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: this file is not just about shopify urls. Maybe rename it to |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import type { Page } from '@playwright/test' | ||
| import { getShopifyStorePassword } from '../../../../scripts/lib/secrets.ts' | ||
|
|
||
| // A Datadog-owned dev store, password-protected, used only to exercise browser-rum-shopify | ||
| // against a real storefront + checkout + Custom Pixel sandbox. | ||
| export const SHOPIFY_STORE_URL = 'https://custom-pixel-e2e.myshopify.com/' | ||
|
|
||
| // Dev stores gate every page behind a storefront password until unlocked for the session. | ||
| const PASSWORD_PATH = /\/password\/?$/ | ||
|
|
||
| export async function unlockShopifyStorePassword(page: Page): Promise<void> { | ||
| if (!PASSWORD_PATH.test(new URL(page.url()).pathname)) { | ||
| return | ||
| } | ||
|
|
||
| await page.getByRole('textbox', { name: /password/i }).fill(getShopifyStorePassword()) | ||
| await page.getByRole('button', { name: /enter/i }).click() | ||
| await page.waitForURL((url) => !PASSWORD_PATH.test(url.pathname)) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay not related specifically to your PR but how about making a function to reuse with Salesforce? Since it's the same pattern except for
datadog-rum-shopifyvsdatadog_rum_salesforce?