diff --git a/gulpfile.js b/gulpfile.js index 056cf229302a0e..a24c71dff44e2c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -515,6 +515,9 @@ function createTestSource(testsName, bot) { case "font": args.push("--fontTest"); break; + case "integration": + args.push("--integration"); + break; default: this.emit("error", new Error("Unknown name: " + testsName)); return null; @@ -1545,6 +1548,13 @@ gulp.task( }) ); +gulp.task( + "integrationtest", + gulp.series("testing-pre", "generic", "components", function () { + return createTestSource("integration"); + }) +); + gulp.task( "fonttest", gulp.series("testing-pre", function () { diff --git a/test/integration-boot.js b/test/integration-boot.js new file mode 100644 index 00000000000000..a7e1987c888b94 --- /dev/null +++ b/test/integration-boot.js @@ -0,0 +1,30 @@ +/* Copyright 2020 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +"use strict"; + +const Jasmine = require("jasmine"); +const jasmine = new Jasmine(); + +jasmine.loadConfig({ + spec_dir: "integration", + spec_files: ["scripting_spec.js", "annotation_spec.js"], +}); + +async function runTests() { + await jasmine.execute(); +} + +exports.runTests = runTests; diff --git a/test/integration/annotation_spec.js b/test/integration/annotation_spec.js new file mode 100644 index 00000000000000..4e54d15df516d5 --- /dev/null +++ b/test/integration/annotation_spec.js @@ -0,0 +1,63 @@ +/* Copyright 2020 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +describe("Annotation highlight", () => { + describe("annotation-highlight.pdf", () => { + let pages; + + beforeAll(async () => { + pages = await Promise.all( + global.integrationSessions.map(async session => { + const page = await session.browser.newPage(); + await page.goto( + `${global.integrationBaseUrl}?file=/test/pdfs/annotation-highlight.pdf` + ); + await page.bringToFront(); + await page.waitForSelector("[data-annotation-id='19R']", { + timeout: 0, + }); + return page; + }) + ); + }, 100000); + + afterAll(async () => { + await Promise.all( + pages.map(async page => { + await page.close(); + }) + ); + }); + + it("must show a popup on mouseover", async () => { + await Promise.all( + pages.map(async page => { + let hidden = await page.$eval( + "[data-annotation-id='21R']", + el => el.hidden + ); + expect(hidden).toEqual(true); + await page.hover("[data-annotation-id='19R']"); + await page.waitForTimeout(100); + hidden = await page.$eval( + "[data-annotation-id='21R']", + el => el.hidden + ); + expect(hidden).toEqual(false); + }) + ); + }); + }); +}); diff --git a/test/integration/scripting_spec.js b/test/integration/scripting_spec.js new file mode 100644 index 00000000000000..2219a67f6b8782 --- /dev/null +++ b/test/integration/scripting_spec.js @@ -0,0 +1,66 @@ +/* Copyright 2020 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +describe("Interaction", () => { + describe("160F-2019.pdf", () => { + let pages; + + beforeAll(async () => { + pages = await Promise.all( + global.integrationSessions.map(async session => { + const page = await session.browser.newPage(); + await page.goto( + `${global.integrationBaseUrl}?file=/test/pdfs/160F-2019.pdf` + ); + await page.bringToFront(); + await page.waitForSelector("#\\34 16R", { + timeout: 0, + }); + return page; + }) + ); + }, 100000); + + afterAll(async () => { + await Promise.all( + pages.map(async page => { + await page.close(); + }) + ); + }); + + it("must format the field with 2 digits and leave field with a click", async () => { + await Promise.all( + pages.map(async page => { + await page.type("#\\34 16R", "3.14159", { delay: 200 }); + await page.click("#\\34 19R"); + const text = await page.$eval("#\\34 16R", el => el.value); + expect(text).toEqual("3.14"); + }) + ); + }); + + it("must format the field with 2 digits and leave field with a TAB", async () => { + await Promise.all( + pages.map(async page => { + await page.type("#\\34 22R", "2.7182818", { delay: 200 }); + await page.keyboard.press("Tab"); + const text = await page.$eval("#\\34 22R", el => el.value); + expect(text).toEqual("2.72"); + }) + ); + }); + }); +}); diff --git a/test/test.js b/test/test.js index b05a7e70845baa..ba5b405721ebce 100644 --- a/test/test.js +++ b/test/test.js @@ -254,7 +254,7 @@ function startRefTest(masterMode, showRefImages) { onAllSessionsClosed = finalize; const startUrl = `http://${host}:${server.port}/test/test_slave.html`; - startBrowsers(startUrl, function (session) { + startBrowsers(function (session) { session.masterMode = masterMode; session.taskResults = {}; session.tasks = {}; @@ -271,7 +271,7 @@ function startRefTest(masterMode, showRefImages) { session.numEqNoSnapshot = 0; session.numEqFailures = 0; monitorBrowserTimeout(session, handleSessionTimeout); - }); + }, makeTestUrl(startUrl)); } function checkRefsTmp() { if (masterMode && fs.existsSync(refsTmpDir)) { @@ -670,11 +670,9 @@ function refTestPostHandler(req, res) { return true; } -function startUnitTest(testUrl, name) { - var startTime = Date.now(); - startServer(); - server.hooks.POST.push(unitTestPostHandler); - onAllSessionsClosed = function () { +function onAllSessionsClosedAfterTests(name) { + const startTime = Date.now(); + return function () { stopServer(); var numRuns = 0, numErrors = 0; @@ -693,12 +691,46 @@ function startUnitTest(testUrl, name) { var runtime = (Date.now() - startTime) / 1000; console.log(name + " tests runtime was " + runtime.toFixed(1) + " seconds"); }; +} + +function makeTestUrl(startUrl) { + return function (browserName) { + const queryParameters = + `?browser=${encodeURIComponent(browserName)}` + + `&manifestFile=${encodeURIComponent("/test/" + options.manifestFile)}` + + `&testFilter=${JSON.stringify(options.testfilter)}` + + `&delay=${options.statsDelay}` + + `&masterMode=${options.masterMode}`; + return startUrl + queryParameters; + }; +} + +function startUnitTest(testUrl, name) { + onAllSessionsClosed = onAllSessionsClosedAfterTests(name); + startServer(); + server.hooks.POST.push(unitTestPostHandler); const startUrl = `http://${host}:${server.port}${testUrl}`; - startBrowsers(startUrl, function (session) { + startBrowsers(function (session) { session.numRuns = 0; session.numErrors = 0; + }, makeTestUrl(startUrl)); +} + +function startIntegrationTest() { + onAllSessionsClosed = onAllSessionsClosedAfterTests("integration"); + startServer(); + + const { runTests } = require("./integration-boot.js"); + startBrowsers(); + global.integrationBaseUrl = `http://${host}:${server.port}/web/viewer.html`; + global.integrationSessions = sessions; + + Promise.all(sessions.map(session => session.browserPromise)).then(() => { + runTests(); }); + + sessions.forEach(session => closeSession(session.name)); } function unitTestPostHandler(req, res) { @@ -768,7 +800,7 @@ function unitTestPostHandler(req, res) { return true; } -async function startBrowser(browserName, startUrl) { +async function startBrowser(browserName, startUrl = "") { const revisions = require("puppeteer/lib/cjs/puppeteer/revisions.js") .PUPPETEER_REVISIONS; const wantedRevision = @@ -790,18 +822,37 @@ async function startBrowser(browserName, startUrl) { } } - const browser = await puppeteer.launch({ + const options = { product: browserName, headless: false, defaultViewport: null, - }); - const pages = await browser.pages(); - const page = pages[0]; - await page.goto(startUrl, { timeout: 0 }); + ignoreDefaultArgs: ["--disable-extensions"], + }; + + if (browserName === "chrome") { + // avoid crash + options.args = ["--no-sandbox", "--disable-setuid-sandbox"]; + } + + if (browserName === "firefox") { + options.extraPrefsFirefox = { + // avoid to have a prompt when leaving a page with a form + "dom.disable_beforeunload": true, + }; + } + + const browser = await puppeteer.launch(options); + + if (startUrl) { + const pages = await browser.pages(); + const page = pages[0]; + await page.goto(startUrl, { timeout: 0 }); + } + return browser; } -function startBrowsers(rootUrl, initSessionCallback) { +function startBrowsers(initSessionCallback, makeStartUrl = null) { const browserNames = options.noChrome ? ["firefox"] : ["firefox", "chrome"]; sessions = []; @@ -820,16 +871,9 @@ function startBrowsers(rootUrl, initSessionCallback) { closed: false, }; sessions.push(session); + const startUrl = makeStartUrl ? makeStartUrl(browserName) : ""; - const queryParameters = - `?browser=${encodeURIComponent(browserName)}` + - `&manifestFile=${encodeURIComponent("/test/" + options.manifestFile)}` + - `&testFilter=${JSON.stringify(options.testfilter)}` + - `&delay=${options.statsDelay}` + - `&masterMode=${options.masterMode}`; - const startUrl = rootUrl + queryParameters; - - startBrowser(browserName, startUrl) + session.browserPromise = startBrowser(browserName, startUrl) .then(function (browser) { session.browser = browser; if (initSessionCallback) { @@ -920,6 +964,8 @@ function main() { }); } else if (options.fontTest) { startUnitTest("/test/font/font_test.html", "font"); + } else if (options.integration) { + startIntegrationTest(); } else { startRefTest(options.masterMode, options.reftest); } diff --git a/web/app_options.js b/web/app_options.js index caf2455caed686..5a27818841d074 100644 --- a/web/app_options.js +++ b/web/app_options.js @@ -67,7 +67,7 @@ const defaultOptions = { }, enableScripting: { /** @type {boolean} */ - value: false, + value: typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION"), kind: OptionKind.VIEWER + OptionKind.PREFERENCE, }, enableWebGL: {