Skip to content
Merged
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
13 changes: 11 additions & 2 deletions .github/workflows/tests-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -253,20 +253,26 @@ jobs:
E2E_ACCOUNT_PASSWORD: ${{ secrets.E2E_ACCOUNT_PASSWORD }}
E2E_STORE_FQDN: ${{ secrets.E2E_STORE_FQDN }}
E2E_ORG_ID: ${{ secrets.E2E_ORG_ID }}
E2E_LOADTEST_HEADER: ${{ secrets.E2E_LOADTEST_HEADER }}
run: pnpm exec playwright test --shard ${{ matrix.shard }}
- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report-${{ strategy.job-index }}
path: packages/e2e/playwright-report/
# Playwright traces contain request headers and must not expose E2E_LOADTEST_HEADER.
path: |
packages/e2e/playwright-report/
!packages/e2e/playwright-report/**/*.zip
retention-days: 14
- name: Upload test results
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-results-${{ strategy.job-index }}
path: packages/e2e/test-results/
path: |
packages/e2e/test-results/
!packages/e2e/test-results/**/trace.zip
retention-days: 14

e2e-cleanup:
Expand Down Expand Up @@ -297,6 +303,7 @@ jobs:
E2E_ACCOUNT_EMAIL: ${{ secrets.E2E_ACCOUNT_EMAIL }}
E2E_ACCOUNT_PASSWORD: ${{ secrets.E2E_ACCOUNT_PASSWORD }}
E2E_ORG_ID: ${{ secrets.E2E_ORG_ID }}
E2E_LOADTEST_HEADER: ${{ secrets.E2E_LOADTEST_HEADER }}
run: pnpm --filter e2e exec tsx scripts/prime-browser-auth.ts
- name: Cleanup current-run E2E apps
if: ${{ always() }}
Expand All @@ -305,6 +312,7 @@ jobs:
E2E_ACCOUNT_EMAIL: ${{ secrets.E2E_ACCOUNT_EMAIL }}
E2E_ACCOUNT_PASSWORD: ${{ secrets.E2E_ACCOUNT_PASSWORD }}
E2E_ORG_ID: ${{ secrets.E2E_ORG_ID }}
E2E_LOADTEST_HEADER: ${{ secrets.E2E_LOADTEST_HEADER }}
run: |
RUN_TOKEN=$(node -e "process.stdout.write(BigInt(process.env.GITHUB_RUN_ID).toString(36))")
pnpm --filter e2e exec tsx scripts/cleanup-apps.ts --pattern "r${RUN_TOKEN}"
Expand All @@ -315,6 +323,7 @@ jobs:
E2E_ACCOUNT_EMAIL: ${{ secrets.E2E_ACCOUNT_EMAIL }}
E2E_ACCOUNT_PASSWORD: ${{ secrets.E2E_ACCOUNT_PASSWORD }}
E2E_ORG_ID: ${{ secrets.E2E_ORG_ID }}
E2E_LOADTEST_HEADER: ${{ secrets.E2E_LOADTEST_HEADER }}
run: |
RUN_TOKEN=$(node -e "process.stdout.write(BigInt(process.env.GITHUB_RUN_ID).toString(36))")
pnpm --filter e2e exec tsx scripts/cleanup-stores.ts --pattern "r${RUN_TOKEN}"
Expand Down
4 changes: 0 additions & 4 deletions packages/app/src/cli/api/graphql/admin/generated/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,6 @@ export type MetaobjectAdminAccess =
| 'MERCHANT_READ'
/** The merchant has read and write access. No other apps have access. */
| 'MERCHANT_READ_WRITE'
/** The merchant and other apps have no access. */
| 'PRIVATE'
/** The merchant and other apps have read-only access. */
| 'PUBLIC_READ'
/** The merchant and other apps have read and write access. */
| 'PUBLIC_READ_WRITE';

Expand Down
4 changes: 4 additions & 0 deletions packages/e2e/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ E2E_ACCOUNT_PASSWORD=
# Required: dedicated e2e org ID
# CI secret: E2E_ORG_ID
E2E_ORG_ID=

# Required: full X-Shopify-Loadtest-<UUID> header name
# CI secret: E2E_LOADTEST_HEADER
E2E_LOADTEST_HEADER=
26 changes: 26 additions & 0 deletions packages/e2e/helpers/loadtest-header.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type {BrowserContext} from '@playwright/test'

const LOADTEST_HEADER_PATTERN = /^X-Shopify-Loadtest-[\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12}$/i
const LOADTEST_HEADER_DOMAINS = ['shopify.com', 'myshopify.com']

export async function addLoadtestHeader(context: BrowserContext): Promise<void> {
const loadtestHeader = process.env.E2E_LOADTEST_HEADER?.trim()

if (!loadtestHeader) {
throw new Error('E2E_LOADTEST_HEADER is required')
}

if (!LOADTEST_HEADER_PATTERN.test(loadtestHeader)) {
throw new Error('E2E_LOADTEST_HEADER must contain a full X-Shopify-Loadtest-<UUID> header name')
}

await context.route(
(requestUrl) =>
LOADTEST_HEADER_DOMAINS.some(
(apexDomain) => requestUrl.hostname === apexDomain || requestUrl.hostname.endsWith(`.${apexDomain}`),
),
async (route, request) => {
await route.continue({headers: {...request.headers(), [loadtestHeader]: 'true'}})
},
)
}
13 changes: 9 additions & 4 deletions packages/e2e/scripts/cleanup-apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* E2E_ACCOUNT_EMAIL — Shopify account email for login
* E2E_ACCOUNT_PASSWORD — Shopify account password
* E2E_ORG_ID — Organization ID to scan for apps
* E2E_LOADTEST_HEADER — Load-test bypass header name
*/

import {config} from 'dotenv'
Expand All @@ -30,11 +31,17 @@ import {getLastPageStatus, navigateToDashboard, refreshIfPageError, trackMainFra
import {deleteAppFromDevDashboard} from '../setup/app.js'
import {uninstallAppFromStore} from '../setup/store.js'
import {completeLogin} from '../helpers/browser-login.js'
import {addLoadtestHeader} from '../helpers/loadtest-header.js'
import type {Page} from '@playwright/test'

// Load .env from packages/e2e/ (not cwd) only if not already configured
const __dirname = path.dirname(fileURLToPath(import.meta.url))
if (!process.env.E2E_ACCOUNT_EMAIL || !process.env.E2E_ACCOUNT_PASSWORD || !process.env.E2E_ORG_ID) {
if (
!process.env.E2E_ACCOUNT_EMAIL ||
!process.env.E2E_ACCOUNT_PASSWORD ||
!process.env.E2E_ORG_ID ||
!process.env.E2E_LOADTEST_HEADER
) {
config({path: path.resolve(__dirname, '../.env')})
}

Expand Down Expand Up @@ -131,11 +138,9 @@ export async function cleanupAllApps(opts: CleanupOptions = {}): Promise<void> {

const browser = await chromium.launch({headless: !opts.headed})
const context = await browser.newContext({
extraHTTPHeaders: {
'X-Shopify-Loadtest-Bf8d22e7-120e-4b5b-906c-39ca9d5499a9': 'true',
},
...(storageStatePath ? {storageState: storageStatePath} : {}),
})
await addLoadtestHeader(context)
context.setDefaultTimeout(BROWSER_TIMEOUT.max)
context.setDefaultNavigationTimeout(BROWSER_TIMEOUT.max)
const page = await context.newPage()
Expand Down
13 changes: 9 additions & 4 deletions packages/e2e/scripts/cleanup-stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* E2E_ACCOUNT_EMAIL — Shopify account email for login
* E2E_ACCOUNT_PASSWORD — Shopify account password
* E2E_ORG_ID — Organization ID to scan for stores
* E2E_LOADTEST_HEADER — Load-test bypass header name
*/

import {config} from 'dotenv'
Expand All @@ -28,6 +29,7 @@ import {BROWSER_TIMEOUT} from '../setup/constants.js'
import {deleteStore, dismissDevConsole, isStoreAppsEmpty} from '../setup/store.js'
import {refreshIfPageError, trackMainFrameStatus} from '../setup/browser.js'
import {completeLogin} from '../helpers/browser-login.js'
import {addLoadtestHeader} from '../helpers/loadtest-header.js'
import {
ListAppDevStores,
type ListAppDevStoresQuery,
Expand All @@ -39,7 +41,12 @@ import type {Page} from '@playwright/test'

// Load .env from packages/e2e/ (not cwd) only if not already configured
const __dirname = path.dirname(fileURLToPath(import.meta.url))
if (!process.env.E2E_ACCOUNT_EMAIL || !process.env.E2E_ACCOUNT_PASSWORD || !process.env.E2E_ORG_ID) {
if (
!process.env.E2E_ACCOUNT_EMAIL ||
!process.env.E2E_ACCOUNT_PASSWORD ||
!process.env.E2E_ORG_ID ||
!process.env.E2E_LOADTEST_HEADER
) {
config({path: path.resolve(__dirname, '../.env')})
}

Expand Down Expand Up @@ -113,11 +120,9 @@ export async function cleanupStores(opts: CleanupStoresOptions = {}): Promise<vo

const browser = await chromium.launch({headless: !opts.headed})
const context = await browser.newContext({
extraHTTPHeaders: {
'X-Shopify-Loadtest-Bf8d22e7-120e-4b5b-906c-39ca9d5499a9': 'true',
},
...(storageStatePath ? {storageState: storageStatePath} : {}),
})
await addLoadtestHeader(context)
context.setDefaultTimeout(BROWSER_TIMEOUT.max)
context.setDefaultNavigationTimeout(BROWSER_TIMEOUT.max)
const page = await context.newPage()
Expand Down
17 changes: 9 additions & 8 deletions packages/e2e/scripts/prime-browser-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,20 @@ import {BROWSER_TIMEOUT, CLI_TIMEOUT} from '../setup/constants.js'
import {executables} from '../setup/env.js'
import {isVisibleWithin} from '../setup/browser.js'
import {completeLogin} from '../helpers/browser-login.js'
import {addLoadtestHeader} from '../helpers/loadtest-header.js'
import {stripAnsi} from '../helpers/strip-ansi.js'
import {waitForText} from '../helpers/wait-for-text.js'
import {execa} from 'execa'
import type {Page} from '@playwright/test'

const __dirname = path.dirname(fileURLToPath(import.meta.url))

if (!process.env.E2E_ACCOUNT_EMAIL || !process.env.E2E_ACCOUNT_PASSWORD || !process.env.E2E_ORG_ID) {
if (
!process.env.E2E_ACCOUNT_EMAIL ||
!process.env.E2E_ACCOUNT_PASSWORD ||
!process.env.E2E_ORG_ID ||
!process.env.E2E_LOADTEST_HEADER
) {
config({path: path.resolve(__dirname, '../.env')})
}

Expand All @@ -38,8 +44,6 @@ interface PrimeBrowserAuthOptions {
orgId?: string
}

const LOADTEST_HEADER = 'X-Shopify-Loadtest-Bf8d22e7-120e-4b5b-906c-39ca9d5499a9'

function isAccountsShopifyUrl(rawUrl: string): boolean {
try {
return new URL(rawUrl).hostname === 'accounts.shopify.com'
Expand Down Expand Up @@ -111,11 +115,8 @@ export async function primeBrowserAuthStorage(opts: PrimeBrowserAuthOptions = {}

const browser = await chromium.launch({headless: !opts.headed})
try {
const context = await browser.newContext({
extraHTTPHeaders: {
[LOADTEST_HEADER]: 'true',
},
})
const context = await browser.newContext()
await addLoadtestHeader(context)
context.setDefaultTimeout(BROWSER_TIMEOUT.max)
context.setDefaultNavigationTimeout(BROWSER_TIMEOUT.max)
const page = await context.newPage()
Expand Down
5 changes: 2 additions & 3 deletions packages/e2e/setup/browser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {cliFixture} from './cli.js'
import {BROWSER_TIMEOUT} from './constants.js'
import {addLoadtestHeader} from '../helpers/loadtest-header.js'
import {chromium, type Locator, type Page} from '@playwright/test'
import * as fs from 'fs'

Expand Down Expand Up @@ -59,11 +60,9 @@ export const browserFixture = cliFixture.extend<{}, {browserPage: Page}>({
const storageStatePath = process.env.E2E_BROWSER_STATE_PATH
const hasValidStorageState = storageStatePath && fs.existsSync(storageStatePath)
const context = await browser.newContext({
extraHTTPHeaders: {
'X-Shopify-Loadtest-Bf8d22e7-120e-4b5b-906c-39ca9d5499a9': 'true',
},
...(hasValidStorageState ? {storageState: storageStatePath} : {}),
})
await addLoadtestHeader(context)
context.setDefaultTimeout(BROWSER_TIMEOUT.max)
context.setDefaultNavigationTimeout(BROWSER_TIMEOUT.max)
const page = await context.newPage()
Expand Down
8 changes: 3 additions & 5 deletions packages/e2e/setup/global-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {CLI_TIMEOUT, BROWSER_TIMEOUT} from './constants.js'
import {stripAnsi} from '../helpers/strip-ansi.js'
import {waitForText} from '../helpers/wait-for-text.js'
import {completeLogin} from '../helpers/browser-login.js'
import {addLoadtestHeader} from '../helpers/loadtest-header.js'
import {execa} from 'execa'
import {chromium, type Page} from '@playwright/test'
import * as path from 'path'
Expand Down Expand Up @@ -103,11 +104,8 @@ export default async function globalSetup() {
// Complete login in a headless browser
const browser = await chromium.launch({headless: !process.env.E2E_HEADED})
try {
const context = await browser.newContext({
extraHTTPHeaders: {
'X-Shopify-Loadtest-Bf8d22e7-120e-4b5b-906c-39ca9d5499a9': 'true',
},
})
const context = await browser.newContext()
await addLoadtestHeader(context)
context.setDefaultTimeout(BROWSER_TIMEOUT.max)
context.setDefaultNavigationTimeout(BROWSER_TIMEOUT.max)
const page = await context.newPage()
Expand Down
Loading