From f9aeed26f6c501dfb40d00f66e3b23ad8aabb996 Mon Sep 17 00:00:00 2001 From: lardo_de_arnaud Date: Wed, 3 Dec 2025 16:35:21 +0000 Subject: [PATCH 1/6] AB#108653 added jsdoc --- src/testUtils.js | 81 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 13 deletions(-) diff --git a/src/testUtils.js b/src/testUtils.js index 3fa8e37..a08f595 100644 --- a/src/testUtils.js +++ b/src/testUtils.js @@ -1,10 +1,22 @@ import { expect } from '@playwright/test' -// Return a valid URL of the test course or account - assertVariables.js will -// ensure these env vars exist and are normalised. -export const TEST_URL = process.env.CANVAS_HOST + "/" + process.env.TEST_PATH +/** + * Normalised test URL built from `CANVAS_HOST` and `TEST_PATH`. + * `src/setup/assertVariables.js` ensures these env vars exist and are normalised. + * @type {string} + */ +export const TEST_URL = process.env.CANVAS_HOST + '/' + process.env.TEST_PATH +/** + * Perform an LTI login by requesting a session token and navigating the page + * to the returned session URL. + * @param {import('@playwright/test').APIRequestContext} request - Playwright request context + * @param {import('@playwright/test').Page} page - Playwright page to navigate + * @param {string} host - Canvas host (base URL) + * @param {string} token - OAuth bearer token + * @returns {Promise} + */ export const login = async (request, page, host, token) => { await Promise.resolve( await request.get(`${host}/login/session_token`, { @@ -23,7 +35,18 @@ export const login = async (request, page, host, token) => { ) } -export const grantAccessIfNeeded = async(page, context, toolUrl) => { + +/** + * Visit `toolUrl` and complete the grant-access flow if the tool requests it. + * Navigates the page into the LTI tool, waits for loading to finish and + * resolves whether the tool requires an explicit grant. If required, the + * `grantAccess` helper is used to complete the flow. + * + * @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 + */ +export const grantAccessIfNeeded = async (page, context, toolUrl) => { await page.goto(toolUrl) const ltiToolFrame = getLtiIFrame(page) @@ -36,16 +59,23 @@ export const grantAccessIfNeeded = async(page, context, toolUrl) => { const needsGrantAccess = await Promise.race([ ltiToolFrame.getByText('Please Grant Access').waitFor() - .then(() => { return true } ), + .then(() => { return true }), waitForNoSpinners(ltiToolFrame, 3000) - .then(() => { return false } ) + .then(() => { return false }) ]) - if(needsGrantAccess){ + if (needsGrantAccess) { await grantAccess(context, ltiToolFrame) } } +/** + * Complete the grant access flow by clicking the authorise button in the + * popup page. Intended to be used by `grantAccessIfNeeded`. + * + * @param {import('@playwright/test').BrowserContext} context - Playwright browser context + * @param {import('@playwright/test').FrameLocator} frameLocator - Locator for the LTI frame + */ const grantAccess = async (context, frameLocator) => { const button = await frameLocator.getByRole('button') const [newPage] = await Promise.all([ @@ -53,32 +83,57 @@ const grantAccess = async (context, frameLocator) => { button.click() ]) - const submit = await newPage.getByRole('button', {name: /Authori[sz]e/}) + const submit = await newPage.getByRole('button', { name: /Authori[sz]e/ }) await submit.click() - const close = await newPage.getByText('Close', {exact: true}) + const close = await newPage.getByText('Close', { exact: true }) await close.click() } + +/** + * Return the frame locator for the LTI launch iframe. + * @param {import('@playwright/test').Page} page - Playwright page + * @returns {import('@playwright/test').FrameLocator} + */ export const getLtiIFrame = (page) => { return page.frameLocator('iframe[data-lti-launch="true"]') } let screenshotCount = 1 +/** + * Take a screenshot of the provided locator and save it into the test + * output directory. Files are numbered sequentially for the duration of the + * process. + * + * @param {import('@playwright/test').Locator} locator - Locator to screenshot + * @param {{ outputDir: string }} testInfo - Playwright `testInfo` object (only `outputDir` used) + */ export const screenshot = async (locator, testInfo) => { - await locator.screenshot({path: `${testInfo.outputDir}/${screenshotCount}.png`, fullPage: true}) + await locator.screenshot({ path: `${testInfo.outputDir}/${screenshotCount}.png`, fullPage: true }) screenshotCount++ } +/** + * Dismiss the beta warning banner if present on the current page. + * @param {import('@playwright/test').Page} page - Playwright page + */ export const dismissBetaBanner = async (page) => { if (page.url().includes('beta')) { const banner = page.getByRole('button', { name: 'Close warning' }) if (await banner.isVisible()) { - await page.getByRole('button', {name: 'Close warning'}).click(); + await page.getByRole('button', { name: 'Close warning' }).click() } } } +/** + * Wait for any `.view-spinner` elements inside the supplied frame locator to + * disappear. Optionally provide an initial delay before checking. + * + * @param {import('@playwright/test').FrameLocator} frameLocator - Frame locator to query + * @param {number} [initialDelay=1000] - milliseconds to wait before starting checks + */ export const waitForNoSpinners = async (frameLocator, initialDelay = 1000) => { - await new Promise(r => setTimeout(r, initialDelay)); - await expect(frameLocator.locator('.view-spinner')).toHaveCount(0, { timeout: 10000 }); + await new Promise(r => setTimeout(r, initialDelay)) + await expect(frameLocator.locator('.view-spinner')).toHaveCount(0, { timeout: 10000 }) } \ No newline at end of file From d14599e64d9a735b3fea6cf51d5897ca5efe66a2 Mon Sep 17 00:00:00 2001 From: Adam Marshall Date: Wed, 3 Dec 2025 17:30:02 +0000 Subject: [PATCH 2/6] Update src/testUtils.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/testUtils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/testUtils.js b/src/testUtils.js index a08f595..9a94bc1 100644 --- a/src/testUtils.js +++ b/src/testUtils.js @@ -132,6 +132,7 @@ export const dismissBetaBanner = async (page) => { * * @param {import('@playwright/test').FrameLocator} frameLocator - Frame locator to query * @param {number} [initialDelay=1000] - milliseconds to wait before starting checks + * @returns {Promise} */ export const waitForNoSpinners = async (frameLocator, initialDelay = 1000) => { await new Promise(r => setTimeout(r, initialDelay)) From 534b615a0975956f774c9f796a389a4bc92ac726 Mon Sep 17 00:00:00 2001 From: Adam Marshall Date: Wed, 3 Dec 2025 17:30:17 +0000 Subject: [PATCH 3/6] Update src/testUtils.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/testUtils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/testUtils.js b/src/testUtils.js index 9a94bc1..e9a6539 100644 --- a/src/testUtils.js +++ b/src/testUtils.js @@ -45,6 +45,7 @@ export const login = async (request, page, host, token) => { * @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 + * @returns {Promise} */ export const grantAccessIfNeeded = async (page, context, toolUrl) => { await page.goto(toolUrl) From 13f239f54b5df039c65e18e4d82a3a277800dd8f Mon Sep 17 00:00:00 2001 From: Adam Marshall Date: Wed, 3 Dec 2025 17:30:42 +0000 Subject: [PATCH 4/6] Update src/testUtils.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/testUtils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/testUtils.js b/src/testUtils.js index e9a6539..cb357e9 100644 --- a/src/testUtils.js +++ b/src/testUtils.js @@ -76,6 +76,7 @@ export const grantAccessIfNeeded = async (page, context, toolUrl) => { * * @param {import('@playwright/test').BrowserContext} context - Playwright browser context * @param {import('@playwright/test').FrameLocator} frameLocator - Locator for the LTI frame + * @returns {Promise} */ const grantAccess = async (context, frameLocator) => { const button = await frameLocator.getByRole('button') From 535bc291eefd5eeebd80362eacafc1eff70585bf Mon Sep 17 00:00:00 2001 From: Adam Marshall Date: Wed, 3 Dec 2025 17:30:56 +0000 Subject: [PATCH 5/6] Update src/testUtils.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/testUtils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/testUtils.js b/src/testUtils.js index cb357e9..1e8298b 100644 --- a/src/testUtils.js +++ b/src/testUtils.js @@ -118,6 +118,7 @@ export const screenshot = async (locator, testInfo) => { /** * Dismiss the beta warning banner if present on the current page. * @param {import('@playwright/test').Page} page - Playwright page + * @returns {Promise} */ export const dismissBetaBanner = async (page) => { if (page.url().includes('beta')) { From 410de62540e81c1087e82d9a3948b87f9b466090 Mon Sep 17 00:00:00 2001 From: Adam Marshall Date: Wed, 3 Dec 2025 17:31:05 +0000 Subject: [PATCH 6/6] Update src/testUtils.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/testUtils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/testUtils.js b/src/testUtils.js index 1e8298b..75ac350 100644 --- a/src/testUtils.js +++ b/src/testUtils.js @@ -109,6 +109,7 @@ let screenshotCount = 1 * * @param {import('@playwright/test').Locator} locator - Locator to screenshot * @param {{ outputDir: string }} testInfo - Playwright `testInfo` object (only `outputDir` used) + * @returns {Promise} */ export const screenshot = async (locator, testInfo) => { await locator.screenshot({ path: `${testInfo.outputDir}/${screenshotCount}.png`, fullPage: true })