diff --git a/test/integration/stamp_editor_spec.mjs b/test/integration/stamp_editor_spec.mjs index fed856d8627a2..303d9e2d0b165 100644 --- a/test/integration/stamp_editor_spec.mjs +++ b/test/integration/stamp_editor_spec.mjs @@ -16,6 +16,7 @@ import { applyFunctionToEditor, awaitPromise, + clearInput, closePages, copy, copyToClipboard, @@ -24,6 +25,7 @@ import { getFirstSerialized, getRect, getSerialized, + isVisible, kbBigMoveDown, kbBigMoveRight, kbSelectAll, @@ -39,6 +41,7 @@ import { waitForSelectedEditor, waitForSerialized, waitForStorageEntries, + waitForUnselectedEditor, } from "./test_utils.mjs"; import { fileURLToPath } from "url"; import fs from "fs"; @@ -835,4 +838,228 @@ describe("Stamp Editor", () => { } }); }); + + describe("New alt-text flow", () => { + let pages; + + beforeAll(async () => { + pages = await loadAndWait( + "empty.pdf", + ".annotationEditorLayer", + null, + null, + { + enableAltText: true, + enableUpdatedAddImage: true, + enableGuessAltText: true, + } + ); + }); + + afterEach(async () => { + for (const [, page] of pages) { + if (await isVisible(page, "#newAltTextDialog")) { + await page.keyboard.press("Escape"); + await page.waitForSelector("#newAltTextDisclaimer", { + visible: false, + }); + } + await page.evaluate(() => { + window.uiManager.reset(); + }); + // Disable editing mode. + await switchToStamp(page, /* disable */ true); + } + }); + + afterAll(async () => { + await closePages(pages); + }); + + it("must check the new alt text flow (part 1)", async () => { + // Run sequentially to avoid clipboard issues. + for (const [browserName, page] of pages) { + await switchToStamp(page); + + // Add an image. + await copyImage(page, "../images/firefox_logo.png", 0); + const editorSelector = getEditorSelector(0); + await page.waitForSelector(editorSelector); + await waitForSerialized(page, 1); + + // Wait for the dialog to be visible. + await page.waitForSelector("#newAltTextDialog", { visible: true }); + // Wait for the spinner to be visible. + await page.waitForSelector("#newAltTextDescriptionContainer.loading"); + // Check we've the disclaimer. + await page.waitForSelector("#newAltTextDisclaimer", { visible: true }); + + // Check that the dialog has the correct title: "Edit..." + await page.waitForFunction( + "document.getElementById('newAltTextTitle').textContent.startsWith('Edit')" + ); + + // Check that AI guessed the correct alt text. + await page.waitForFunction( + `document.getElementById("newAltTextDescriptionTextarea").value === + "Fake alt text"` + ); + + // Check that the dialog has the correct title: "Edit..." + await page.waitForFunction( + "document.getElementById('newAltTextTitle').textContent.startsWith('Edit')" + ); + + // Check we've the disclaimer. + await page.waitForSelector("#newAltTextDisclaimer", { visible: true }); + + // Clear the input and check that the title changes to "Add..." + await clearInput( + page, + "#newAltTextDescriptionTextarea", + /* waitForInputEvent = */ true + ); + await page.waitForFunction( + "document.getElementById('newAltTextTitle').textContent.startsWith('Add')" + ); + + // Check we haven't the disclaimer. + await page.waitForSelector("#newAltTextDisclaimer", { visible: false }); + + // Add a new alt text and check that the title changes to "Edit..." + await page.type("#newAltTextDescriptionTextarea", "Hello World"); + await page.waitForFunction( + "document.getElementById('newAltTextTitle').textContent.startsWith('Edit')" + ); + + // Check we haven't the disclaimer after the modification. + await page.waitForSelector("#newAltTextDisclaimer", { visible: false }); + + // Click on the Not Now button. + await page.click("#newAltTextNotNow"); + await page.waitForSelector("#newAltTextDialog", { visible: false }); + await waitForSelectedEditor(page, editorSelector); + + // Wait for the alt-text button to be visible. + const buttonSelector = `${editorSelector} button.altText.new`; + await page.waitForSelector(buttonSelector, { visible: true }); + + // Check the text in the button. + let text = await page.evaluate( + sel => document.querySelector(sel).textContent, + buttonSelector + ); + let ariaLabel = await page.evaluate( + sel => document.querySelector(sel).getAttribute("aria-label"), + buttonSelector + ); + expect(text === ariaLabel && text) + .withContext(`In ${browserName}`) + .toEqual("Review alt text"); + + // Unselect and select the editor and check that the badge is visible. + await page.keyboard.press("Escape"); + await waitForUnselectedEditor(page, editorSelector); + await page.waitForSelector(".editToolbar", { visible: false }); + await page.waitForSelector(".noAltTextBadge", { visible: true }); + + await page.evaluate(() => { + window.uiManager.selectAll(); + }); + await waitForSelectedEditor(page, editorSelector); + await page.waitForSelector(".editToolbar", { visible: true }); + await page.waitForSelector(".noAltTextBadge", { visible: false }); + + // Click on the Review button. + await page.click(buttonSelector); + await page.waitForSelector("#newAltTextDialog", { visible: true }); + + // Check that the dialog has the correct title: "Edit..." + await page.waitForFunction( + "document.getElementById('newAltTextTitle').textContent.startsWith('Edit')" + ); + + // Click on create automatically toggle button. + await page.click("#newAltTextCreateAutomaticallyButton"); + await clearInput( + page, + "#newAltTextDescriptionTextarea", + /* waitForInputEvent = */ true + ); + + // Save the empty text. + await page.click("#newAltTextSave"); + await page.waitForSelector("#newAltTextDialog", { visible: false }); + await waitForSelectedEditor(page, editorSelector); + await page.waitForSelector(buttonSelector, { visible: true }); + + // Check the text in the button. + text = await page.evaluate( + sel => document.querySelector(sel).textContent, + buttonSelector + ); + ariaLabel = await page.evaluate( + sel => document.querySelector(sel).getAttribute("aria-label"), + buttonSelector + ); + expect(text === ariaLabel && text) + .withContext(`In ${browserName}`) + .toEqual("Missing alt text"); + + // Unselect and select the editor and check that the badge is visible. + await page.keyboard.press("Escape"); + await waitForUnselectedEditor(page, editorSelector); + await page.waitForSelector(".editToolbar", { visible: false }); + await page.waitForSelector(".noAltTextBadge", { visible: true }); + await page.evaluate(() => { + window.uiManager.selectAll(); + }); + await waitForSelectedEditor(page, editorSelector); + await page.waitForSelector(".editToolbar", { visible: true }); + await page.waitForSelector(".noAltTextBadge", { visible: false }); + + // Click on the Review button. + await page.click(buttonSelector); + await page.waitForSelector("#newAltTextDialog", { visible: true }); + + await page.waitForFunction( + "document.getElementById('newAltTextTitle').textContent.startsWith('Add')" + ); + // Add a new alt text and check that the title changes to "Edit..." + await page.type("#newAltTextDescriptionTextarea", "Hello World"); + await page.waitForFunction( + "document.getElementById('newAltTextTitle').textContent.startsWith('Edit')" + ); + + // Click on the Save button. + await page.click("#newAltTextSave"); + await page.waitForSelector("#newAltTextDialog", { visible: false }); + + // Check the text in the button. + text = await page.evaluate( + sel => document.querySelector(sel).firstChild.textContent, + buttonSelector + ); + ariaLabel = await page.evaluate( + sel => document.querySelector(sel).getAttribute("aria-label"), + buttonSelector + ); + expect(text === ariaLabel && text) + .withContext(`In ${browserName}`) + .toEqual("Alt text added"); + + await page.hover(buttonSelector); + + // Wait for the tooltip to be visible. + const tooltipSelector = `${buttonSelector} .tooltip`; + await page.waitForSelector(tooltipSelector, { visible: true }); + + const tooltipText = await page.evaluate( + sel => document.querySelector(`${sel}`).textContent, + tooltipSelector + ); + expect(tooltipText).toEqual("Hello World"); + } + }); + }); }); diff --git a/test/integration/test_utils.mjs b/test/integration/test_utils.mjs index be2bbbcb54007..89281a33b6a60 100644 --- a/test/integration/test_utils.mjs +++ b/test/integration/test_utils.mjs @@ -84,6 +84,13 @@ function closePages(pages) { return Promise.all(pages.map(([_, page]) => closeSinglePage(page))); } +function isVisible(page, selector) { + return page.evaluate( + sel => document.querySelector(sel)?.checkVisibility(), + selector + ); +} + async function closeSinglePage(page) { // Avoid to keep something from a previous test. await page.evaluate(async () => { @@ -119,13 +126,23 @@ function waitForTimeout(milliseconds) { }); } -async function clearInput(page, selector) { - await page.click(selector); - await kbSelectAll(page); - await page.keyboard.press("Backspace"); - await page.waitForFunction( - `document.querySelector('${selector}').value === ""` - ); +async function clearInput(page, selector, waitForInputEvent = false) { + const action = async () => { + await page.click(selector); + await kbSelectAll(page); + await page.keyboard.press("Backspace"); + await page.waitForFunction( + `document.querySelector('${selector}').value === ""` + ); + }; + return waitForInputEvent + ? waitForEvent({ + page, + eventName: "input", + action, + selector, + }) + : action(); } function getSelector(id) { @@ -711,6 +728,7 @@ export { getSerialized, getSpanRectFromText, hover, + isVisible, kbBigMoveDown, kbBigMoveLeft, kbBigMoveRight, diff --git a/web/app.js b/web/app.js index d4d18516eaafc..1a6969ef59f87 100644 --- a/web/app.js +++ b/web/app.js @@ -385,6 +385,21 @@ const PDFViewerApplication = { parseInt(params.get("spreadmodeonload")) ); } + if (params.has("enablealttext")) { + AppOptions.set("enableAltText", params.get("enablealttext") === "true"); + } + if (params.has("enableupdatedaddimage")) { + AppOptions.set( + "enableUpdatedAddImage", + params.get("enableupdatedaddimage") === "true" + ); + } + if (params.has("enableguessalttext")) { + AppOptions.set( + "enableGuessAltText", + params.get("enableguessalttext") === "true" + ); + } } },