diff --git a/gulpfile.js b/gulpfile.js index 8e95a525768052..a151d473d748a4 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -506,6 +506,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; @@ -1544,6 +1547,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/annotation.js b/test/integration/annotation.js new file mode 100644 index 00000000000000..1cf95ad6fc4f99 --- /dev/null +++ b/test/integration/annotation.js @@ -0,0 +1,35 @@ +/* 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. + */ + +/* global assert */ + +describe( + "Annotation highlight", + "annotation-highlight.pdf", + "[data-annotation-id='19R']", + function () { + it("must show a popup on mouseover", async function (page) { + let hidden = await page.$eval( + "[data-annotation-id='21R']", + el => el.hidden + ); + assert.equal(hidden, true); + await page.hover("[data-annotation-id='19R']"); + await page.waitForTimeout(100); + hidden = await page.$eval("[data-annotation-id='21R']", el => el.hidden); + assert.equal(hidden, false); + }); + } +); diff --git a/test/integration/integration-boot.js b/test/integration/integration-boot.js new file mode 100644 index 00000000000000..44c279c53cb7b9 --- /dev/null +++ b/test/integration/integration-boot.js @@ -0,0 +1,97 @@ +/* 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"; + +function loadTestFiles() { + ["./scripting.js", "./annotation.js"].map(function (moduleName) { + return require(moduleName); + }); +} + +const describes = []; + +function make_selector(id) { + if (id.length >= 1 && id.charAt(0) >= "0" && id.charAt(0) <= "9") { + return `#\\3${id.charAt(0)} ${id.slice(1)}`; + } + return id; +} + +global.make_selector = make_selector; +global.describe = (desc, filename, selector, callback) => { + // filename: test filename + // selector: html element selector to wait for before starting the test + const describeObj = { + desc, + filename, + selector, + itCallbacks: [], + }; + describes.push(describeObj); + + global.it = (itDesc, itCallback) => { + describeObj.itCallbacks.push([itDesc, itCallback]); + }; + callback(); +}; +global.assert = require("assert"); + +async function runTests(baseUrl, session) { + // Load the test files + loadTestFiles(); + + const itCallbacks = []; + + // Create the pages + await Promise.all( + describes.map(desc => + (async function () { + const page = await session.browser.newPage(); + await page.goto(`${baseUrl}?file=/test/pdfs/${desc.filename}`); + desc.itCallbacks.forEach(([itDesc, cb]) => { + itCallbacks.push([ + itDesc, + async () => { + await page.bringToFront(); + await page.waitForSelector(make_selector(desc.selector), { + timeout: 0, + }); + await cb(page); + }, + ]); + }); + })() + ) + ); + + // Execute test sequentially + await itCallbacks.reduce(async (p, [desc, cb]) => { + await p; + try { + session.numRuns++; + await cb(); + console.log(`TEST-PASSED | "${desc}" | in ${session.name}`); + } catch (error) { + session.numErrors++; + const stack = error.stack.split("\n"); + console.log(`TEST-UNEXPECTED-FAIL | "${desc}" | in ${session.name}`); + console.log(`${stack[0]}`); + console.log(`${stack[1]}`); + } + }, Promise.resolve()); +} + +exports.runTests = runTests; diff --git a/test/integration/scripting.js b/test/integration/scripting.js new file mode 100644 index 00000000000000..f91acfa961cc88 --- /dev/null +++ b/test/integration/scripting.js @@ -0,0 +1,32 @@ +/* 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. + */ + +/* global assert, make_selector */ + +describe("Interaction", "160F-2019.pdf", "416R", function () { + it("must format the field with 2 digits and leave field with a click", async function (page) { + await page.type(make_selector("416R"), "3.14159", { delay: 200 }); + await page.click(make_selector("419R")); + const text = await page.$eval(make_selector("416R"), el => el.value); + assert.equal(text, "3.14"); + }); + + it("must format the field with 2 digits and leave field with a TAB", async function (page) { + await page.type(make_selector("422R"), "2.7182818", { delay: 200 }); + await page.keyboard.press("Tab"); + const text = await page.$eval(make_selector("422R"), el => el.value); + assert.equal(text, "2.72"); + }); +}); diff --git a/test/test.js b/test/test.js index b05a7e70845baa..6d4ffa9fd16178 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,11 +691,42 @@ 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 baseUrl = `http://${host}:${server.port}/web/viewer.html`; + const { runTests } = require("./integration/integration-boot.js"); + startBrowsers(async function (session) { + session.numRuns = 0; + session.numErrors = 0; + await runTests(baseUrl, session); }); } @@ -768,7 +797,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 +819,36 @@ 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 }); + }; + + 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 = undefined) { const browserNames = options.noChrome ? ["firefox"] : ["firefox", "chrome"]; sessions = []; @@ -820,20 +867,18 @@ function startBrowsers(rootUrl, initSessionCallback) { closed: false, }; sessions.push(session); - - 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; + const startUrl = makeStartUrl ? makeStartUrl(browserName) : ""; startBrowser(browserName, startUrl) .then(function (browser) { session.browser = browser; if (initSessionCallback) { - initSessionCallback(session); + const res = initSessionCallback(session); + if (res instanceof Promise) { + res.then(() => { + closeSession(browserName); + }); + } } }) .catch(function (ex) { @@ -920,6 +965,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 d13f2026df311e..de9ac0cc2d39a7 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: {