From e6073c3d4b0c174d1debbb6c13da5ee17eb7a07e Mon Sep 17 00:00:00 2001 From: christopherjohnson Date: Thu, 25 Jun 2026 08:12:40 +0100 Subject: [PATCH 1/3] feat(testUtils): add navigation option to grantAccessIfNeeded and implement cookie dialog dismissal --- README.md | 11 +++++++++++ src/testUtils.js | 28 ++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a823c07..6019f8d 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,17 @@ test.describe('Test deployment', () => { If your LTI tool requires the user to grant access, you must call `grantAccessIfNeeded` in your tests. This is no longer handled during authentication setup. +### Optional: Skip page navigation in `grantAccessIfNeeded` + +By default, `grantAccessIfNeeded` navigates to the tool URL. If your test has already navigated to the tool, pass `{ shouldNavigate: false }` to skip the navigation step: + +```javascript +// Already at the tool URL +await page.goto(TEST_URL) +// Check for grant access flow without re-navigating +await grantAccessIfNeeded(page, context, TEST_URL, { shouldNavigate: false }) +``` + ## Recommended npm scripts ```json diff --git a/src/testUtils.js b/src/testUtils.js index a404b2b..b8caa4c 100644 --- a/src/testUtils.js +++ b/src/testUtils.js @@ -18,10 +18,15 @@ export const TEST_URL = buildTestUrl( * @param {import('@playwright/test').Page} page - Playwright page instance * @param {import('@playwright/test').BrowserContext} context - Playwright browser context * @param {string} toolUrl - URL of the LTI tool to visit + * @param {{shouldNavigate?: boolean}} [options] - Optional configuration + * @param {boolean} [options.shouldNavigate=true] - Whether to navigate to toolUrl (default true) * @returns {Promise} */ -export const grantAccessIfNeeded = async (page, context, toolUrl) => { - await page.goto(toolUrl) +export const grantAccessIfNeeded = async (page, context, toolUrl, options = {}) => { + const { shouldNavigate = true } = options + if (shouldNavigate) { + await page.goto(toolUrl) + } const ltiToolFrame = getLtiIFrame(page) // wait for tool-support loading page @@ -126,4 +131,23 @@ function normalizeUrlParts(host, path) { function buildTestUrl(host, path) { const { host: normalizedHost, path: normalizedPath } = normalizeUrlParts(host, path) return `${normalizedHost}/${normalizedPath}` +} + +/** + * Dismiss the OneTrust cookie dialog by accepting cookies if the banner appears. + * @param {import('@playwright/test').Page} page - Playwright page + * @param {number} [timeoutMs=3000] - Max wait for the dialog accept button + * @returns {Promise} + */ +export const dismissCookieDialog = async (page, timeoutMs = 3000) => { + const dialog = page.getByRole('dialog', { name: 'Cookie Settings' }) + const acceptButton = dialog.getByRole('button', { name: 'Accept' }).first() + const canAccept = await acceptButton + .waitFor({ state: 'visible', timeout: timeoutMs }) + .then(() => { return true }) + .catch(() => { return false }) + + if (canAccept) { + await acceptButton.click() + } } \ No newline at end of file From 67dff7ab2db114c788df5cdb5866d490d50246ee Mon Sep 17 00:00:00 2001 From: christopherjohnson Date: Thu, 25 Jun 2026 08:30:20 +0100 Subject: [PATCH 2/3] feat(testUtils): refactor dismissCookieDialog - noop when not found --- src/testUtils.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/testUtils.js b/src/testUtils.js index b8caa4c..8871dd0 100644 --- a/src/testUtils.js +++ b/src/testUtils.js @@ -141,13 +141,17 @@ function buildTestUrl(host, path) { */ export const dismissCookieDialog = async (page, timeoutMs = 3000) => { const dialog = page.getByRole('dialog', { name: 'Cookie Settings' }) - const acceptButton = dialog.getByRole('button', { name: 'Accept' }).first() - const canAccept = await acceptButton + const dialogVisible = await dialog .waitFor({ state: 'visible', timeout: timeoutMs }) .then(() => { return true }) .catch(() => { return false }) - if (canAccept) { + if (!dialogVisible) { + return + } + + const acceptButton = dialog.getByRole('button', { name: 'Accept' }).first() + if (await acceptButton.isVisible()) { await acceptButton.click() } -} \ No newline at end of file +} From 3a4bbb58d407660aad6149937aca5f3149a9a6eb Mon Sep 17 00:00:00 2001 From: christopherjohnson Date: Thu, 25 Jun 2026 10:33:27 +0100 Subject: [PATCH 3/3] feat(testUtils): refactor navigation support and cookie dialog handler --- README.md | 16 ++++++++++++++-- src/testUtils.js | 34 ++++++++++++---------------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 6019f8d..ba9ffee 100644 --- a/README.md +++ b/README.md @@ -102,13 +102,25 @@ If your LTI tool requires the user to grant access, you must call `grantAccessIf ### Optional: Skip page navigation in `grantAccessIfNeeded` -By default, `grantAccessIfNeeded` navigates to the tool URL. If your test has already navigated to the tool, pass `{ shouldNavigate: false }` to skip the navigation step: +If `toolUrl` is provided, `grantAccessIfNeeded` navigates to it. If your test has already navigated to the tool, omit `toolUrl` to skip the navigation step: ```javascript // Already at the tool URL await page.goto(TEST_URL) // Check for grant access flow without re-navigating -await grantAccessIfNeeded(page, context, TEST_URL, { shouldNavigate: false }) +await grantAccessIfNeeded(page, context) +``` + +### Optional: Register a cookie-dialog handler once per test + +If your environment shows the OneTrust cookie dialog unpredictably, register a locator handler in test setup so it is auto-accepted whenever it appears: + +```javascript +import { registerCookieDialogHandler } from '@oxctl/deployment-test-utils' + +test.beforeEach(async ({ page }) => { + await registerCookieDialogHandler(page) +}) ``` ## Recommended npm scripts diff --git a/src/testUtils.js b/src/testUtils.js index 8871dd0..42a0bdc 100644 --- a/src/testUtils.js +++ b/src/testUtils.js @@ -17,14 +17,11 @@ export const TEST_URL = buildTestUrl( * * @param {import('@playwright/test').Page} page - Playwright page instance * @param {import('@playwright/test').BrowserContext} context - Playwright browser context - * @param {string} toolUrl - URL of the LTI tool to visit - * @param {{shouldNavigate?: boolean}} [options] - Optional configuration - * @param {boolean} [options.shouldNavigate=true] - Whether to navigate to toolUrl (default true) + * @param {string} [toolUrl] - Optional URL of the LTI tool to visit * @returns {Promise} */ -export const grantAccessIfNeeded = async (page, context, toolUrl, options = {}) => { - const { shouldNavigate = true } = options - if (shouldNavigate) { +export const grantAccessIfNeeded = async (page, context, toolUrl) => { + if (toolUrl) { await page.goto(toolUrl) } const ltiToolFrame = getLtiIFrame(page) @@ -134,24 +131,17 @@ function buildTestUrl(host, path) { } /** - * Dismiss the OneTrust cookie dialog by accepting cookies if the banner appears. + * Register a Playwright locator handler that accepts the OneTrust cookie dialog + * whenever it appears on the page. + * + * Call this once per test/page in consumer setup (for example in `beforeEach`). + * * @param {import('@playwright/test').Page} page - Playwright page - * @param {number} [timeoutMs=3000] - Max wait for the dialog accept button * @returns {Promise} */ -export const dismissCookieDialog = async (page, timeoutMs = 3000) => { +export const registerCookieDialogHandler = async (page) => { const dialog = page.getByRole('dialog', { name: 'Cookie Settings' }) - const dialogVisible = await dialog - .waitFor({ state: 'visible', timeout: timeoutMs }) - .then(() => { return true }) - .catch(() => { return false }) - - if (!dialogVisible) { - return - } - - const acceptButton = dialog.getByRole('button', { name: 'Accept' }).first() - if (await acceptButton.isVisible()) { - await acceptButton.click() - } + await page.addLocatorHandler(dialog, async () => { + await dialog.getByRole('button', { name: 'Accept' }).first().click() + }) }