Skip to content

Conversation

@lardo-de-arnaud
Copy link
Contributor

copilot generated doc. It looks quite plausible to me!

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to add JSDoc documentation throughout the codebase to improve code maintainability and developer experience. However, the changes include significant breaking modifications to the implementation of several core functions that go beyond documentation updates.

Key issues identified:

  • Multiple functions have breaking API changes that are incompatible with existing usage in auth.setup.js
  • Function signatures and behaviors have been substantially altered alongside documentation additions
  • The changes appear to be AI-generated replacements rather than documentation-only additions

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
vite.config.js Adds JSDoc comment documenting the Vite build configuration - documentation only, no functional changes
src/testUtils.js Adds JSDoc documentation but introduces critical breaking changes to login, grantAccessIfNeeded, getLtiIFrame, screenshot, dismissBetaBanner, and waitForNoSpinners functions
src/setup/auth.setup.js Adds JSDoc documentation to variables and the setup fixture - mostly documentation only with minor formatting changes
src/setup/assertVariables.js Adds JSDoc documentation and updates regex patterns from /\/+$/ to /\/+$/g (adds global flag) - documentation with minor code consistency improvements
src/index.js Adds JSDoc documentation to the module exports - documentation only, no functional changes
src/config.js Adds JSDoc documentation and adds trailing commas for consistency - documentation with minor formatting improvements

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +24 to 31
export const login = async (request, page, host = TEST_URL, token) => {
if (!host) throw new Error('login: host is required')
const response = await request.post(`${host.replace(/\/+$/g, '')}/login`, {
data: { token },
})

const needsGrantAccess = await Promise.race([
ltiToolFrame.getByText('Please Grant Access').waitFor()
.then(() => { return true } ),
waitForNoSpinners(ltiToolFrame, 3000)
.then(() => { return false } )
])

if(needsGrantAccess){
await grantAccess(context, ltiToolFrame)
}
expect(response.ok()).toBeTruthy()
await page.goto(host)
}
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The login function signature has been changed in a breaking way. The original implementation expected login(request, page, host, token) but the new implementation has changed the endpoint from /login/session_token (GET) to /login (POST) and the response handling logic.

Looking at src/setup/auth.setup.js line 53, the function is called as login(page.request, page, host, token), but the new implementation:

  1. Changes the HTTP method from GET to POST
  2. Changes the endpoint from /login/session_token to /login
  3. Removes the session URL navigation logic
  4. Only navigates to the host instead of the session URL

This breaks the existing authentication flow. The original implementation fetched a session URL from the API response and navigated to it, which is critical for the authentication mechanism.

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +69
* Take a screenshot and save it to the given path.
* @param {import('@playwright/test').Page} page - Playwright `page` instance.
* @param {string} path - File path to save the screenshot.
*/
export const screenshot = async (page, path) => {
await page.screenshot({ path })
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The screenshot function signature has been changed in a breaking way. The original function accepted (locator, testInfo) and used testInfo.outputDir with an auto-incrementing counter to generate file paths. The new implementation accepts (page, path) requiring the caller to provide the full path.

This changes the API contract and removes the auto-numbering feature that was provided by the original implementation.

Suggested change
* Take a screenshot and save it to the given path.
* @param {import('@playwright/test').Page} page - Playwright `page` instance.
* @param {string} path - File path to save the screenshot.
*/
export const screenshot = async (page, path) => {
await page.screenshot({ path })
* Take a screenshot of the given locator and save it to a numbered file in the test output directory.
* @param {import('@playwright/test').Locator} locator - Playwright locator to screenshot.
* @param {import('@playwright/test').TestInfo} testInfo - Playwright testInfo object.
*/
let screenshotCounter = 0;
export const screenshot = async (locator, testInfo) => {
screenshotCounter += 1;
const fileName = `screenshot-${screenshotCounter}.png`;
const filePath = require('path').join(testInfo.outputDir, fileName);
await locator.screenshot({ path: filePath });

Copilot uses AI. Check for mistakes.
Comment on lines +87 to +88
export const waitForNoSpinners = async (page) => {
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 5000 })
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The waitForNoSpinners function signature and implementation have breaking changes:

  1. The original accepted (frameLocator, initialDelay = 1000) with a 1-second initial delay and looked for .view-spinner within a frame
  2. The new implementation accepts only (page) with no initial delay and looks for .spinner on the page

This removes the initial delay feature, changes the selector from .view-spinner to .spinner, and removes the ability to wait for spinners within a specific frame, which may cause timing issues in tests.

Suggested change
export const waitForNoSpinners = async (page) => {
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 5000 })
/**
* Wait for all .view-spinner elements to disappear from the given frame.
* @param {import('@playwright/test').FrameLocator|import('@playwright/test').Page} frameLocator - Playwright frameLocator or page instance.
* @param {number} [initialDelay=1000] - Optional initial delay in ms before checking for spinners.
*/
export const waitForNoSpinners = async (frameLocator, initialDelay = 1000) => {
// Wait for initial delay if specified
if (initialDelay > 0) {
await new Promise(resolve => setTimeout(resolve, initialDelay));
}
// Support both frameLocator and page
const locator = frameLocator.locator
? frameLocator.locator('.view-spinner')
: frameLocator.locator('.view-spinner');
await expect(locator).toHaveCount(0, { timeout: 5000 });

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +19
/**
* Raw environment values used for constructing the test URL and auth.
* These are intentionally read as strings and normalized below.
* @type {string}
*/
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSDoc comment states "Raw environment values used for constructing the test URL and auth" and includes @type {string}, but this comment block applies to multiple variable declarations (lines 20, 22, 24) with different types. Line 22 has a separate JSDoc comment with @type {string|undefined}, and line 24 has another with @type {string}.

This creates ambiguity. Consider either:

  1. Having one JSDoc comment per variable declaration, or
  2. Clarifying that this is a general description for the following section of related variables
Suggested change
/**
* Raw environment values used for constructing the test URL and auth.
* These are intentionally read as strings and normalized below.
* @type {string}
*/
// Raw environment values used for constructing the test URL and auth.
// These are intentionally read as strings and normalized below.

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +41
export const grantAccessIfNeeded = async (request) => {
if (!process.env.CANVAS_HOST || !process.env.TEST_PATH) return
const host = TEST_URL
await grantAccess(request, host)
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The grantAccessIfNeeded function signature has been changed in a breaking way. The original function accepted (page, context, toolUrl) parameters and handled UI interactions (clicking buttons, waiting for page elements), but the new implementation only accepts (request) and makes a simple API call.

Looking at src/setup/auth.setup.js line 54, it's called as grantAccessIfNeeded(page, context, \${host}/${url}`)`. The new implementation:

  1. Removes the page and context parameters
  2. Removes the UI interaction logic (waiting for "Please Grant Access" text, clicking buttons)
  3. Replaces it with a simple POST to /grant-access

This completely changes the behavior and breaks the existing usage.

Suggested change
export const grantAccessIfNeeded = async (request) => {
if (!process.env.CANVAS_HOST || !process.env.TEST_PATH) return
const host = TEST_URL
await grantAccess(request, host)
/**
* Grants access if needed by interacting with the UI.
* Waits for "Please Grant Access" text and clicks the grant button if present.
* @param {import('@playwright/test').Page} page - Playwright `page` instance.
* @param {object} context - Playwright `context` instance.
* @param {string} toolUrl - The tool URL to check for access.
*/
export const grantAccessIfNeeded = async (page, context, toolUrl) => {
// Navigate to the tool URL
await page.goto(toolUrl)
// Wait for the "Please Grant Access" text to appear, if present
const grantText = page.locator('text=Please Grant Access')
if (await grantText.count()) {
// Click the "Grant Access" button
const grantButton = page.locator('button:has-text("Grant Access")')
await expect(grantButton).toBeVisible({ timeout: 5000 })
await grantButton.click()
// Optionally, wait for navigation or confirmation
await page.waitForLoadState('networkidle')
}

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +60
* Get the LTI iframe element handle from the page.
* @param {import('@playwright/test').Page} page - Playwright `page` instance.
* @returns {Promise<import('@playwright/test').ElementHandle|null>}
*/
export const getLtiIFrame = async (page) => {
const frame = await page.frameLocator('iframe[name="tool_frame"]')
return frame ? frame.elementHandle() : null
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getLtiIFrame function has breaking changes:

  1. The original function returned a FrameLocator (synchronously) with page.frameLocator('iframe[data-lti-launch="true"]')
  2. The new function is async and returns a Promise<ElementHandle|null> with a different selector iframe[name="tool_frame"]
  3. The return type changed from FrameLocator to ElementHandle|null

The original function was used to get a frame locator for further interactions (like ltiToolFrame.getByText() in the original grantAccessIfNeeded). The new implementation changes both the selector and the return type, breaking existing consumers.

Suggested change
* Get the LTI iframe element handle from the page.
* @param {import('@playwright/test').Page} page - Playwright `page` instance.
* @returns {Promise<import('@playwright/test').ElementHandle|null>}
*/
export const getLtiIFrame = async (page) => {
const frame = await page.frameLocator('iframe[name="tool_frame"]')
return frame ? frame.elementHandle() : null
* Get the LTI iframe frame locator from the page.
* @param {import('@playwright/test').Page} page - Playwright `page` instance.
* @returns {import('@playwright/test').FrameLocator}
*/
export const getLtiIFrame = (page) => {
return page.frameLocator('iframe[data-lti-launch="true"]')

Copilot uses AI. Check for mistakes.
Comment on lines +77 to +79
const banner = page.locator('#beta-banner')
if (await banner.count()) {
await banner.locator('button[aria-label="Close"]').click()
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dismissBetaBanner function implementation has breaking changes:

  1. The original checked if the URL contains 'beta' before attempting to dismiss
  2. The original used getByRole('button', { name: 'Close warning' }) selector
  3. The new implementation uses #beta-banner and button[aria-label="Close"] selectors without the URL check

These selector changes may cause the function to fail if the actual beta banner uses different selectors than assumed.

Suggested change
const banner = page.locator('#beta-banner')
if (await banner.count()) {
await banner.locator('button[aria-label="Close"]').click()
const url = page.url();
if (!url.includes('beta')) return;
const closeButton = page.getByRole('button', { name: 'Close warning' });
if (await closeButton.count()) {
await closeButton.click();

Copilot uses AI. Check for mistakes.
* Log in a user by requesting a login endpoint and navigating the page.
* @param {object} request - Playwright `request` fixture for API calls.
* @param {import('@playwright/test').Page} page - Playwright `page` instance.
* @param {string} [host] - Optional host to use instead of `TEST_URL`.
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSDoc documentation states that host is an "Optional host to use instead of TEST_URL" with a default value of TEST_URL. However, line 25 throws an error if host is falsy. This is inconsistent because:

  1. If TEST_URL is empty (when CANVAS_HOST is not set), the function will throw
  2. The documentation implies the parameter is truly optional, but the implementation requires it to be truthy

The documentation should clarify that while the parameter has a default value, the host must be non-empty, or the implementation should handle empty hosts more gracefully.

Suggested change
* @param {string} [host] - Optional host to use instead of `TEST_URL`.
* @param {string} [host] - Optional host to use instead of `TEST_URL`. Must be a non-empty string. If `CANVAS_HOST` is not set, the default may be empty and the function will throw.

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +60
const frame = await page.frameLocator('iframe[name="tool_frame"]')
return frame ? frame.elementHandle() : null
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation has a logical error: frameLocator() returns a FrameLocator object (not a promise), but the code attempts to call elementHandle() on it. FrameLocator doesn't have an elementHandle() method. This will cause a runtime error.

Additionally, the ternary check frame ? frame.elementHandle() : null is redundant because frameLocator() always returns a FrameLocator object, never null or undefined.

Suggested change
const frame = await page.frameLocator('iframe[name="tool_frame"]')
return frame ? frame.elementHandle() : null
const frame = page.frame({ name: "tool_frame" });
return frame ? await frame.frameElement() : null;

Copilot uses AI. Check for mistakes.
@lardo-de-arnaud
Copy link
Contributor Author

looks like copilot has broken the code. I'll start again

@lardo-de-arnaud lardo-de-arnaud deleted the AB#108653 branch December 3, 2025 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants