From 46f39479276f787e0fbe6f45156b469e2428c137 Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Thu, 19 Oct 2023 18:25:13 -0700 Subject: [PATCH] [WIP] CLI: Initial version of qunit-browser.js --- .eslintrc.json | 2 +- bin/qunit-browser.js | 216 +++++++++++++++++++++++++++++++++++++++++++ build/utils.js | 2 +- 3 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 bin/qunit-browser.js diff --git a/.eslintrc.json b/.eslintrc.json index f84612bdc..cfbb25e50 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -89,7 +89,7 @@ ], "extends": [".eslintrc.base.js", "plugin:node/recommended"], "parserOptions": { - "ecmaVersion": 2020, + "ecmaVersion": 2021, "sourceType": "script" }, "env": { diff --git a/bin/qunit-browser.js b/bin/qunit-browser.js new file mode 100644 index 000000000..3a01ef80d --- /dev/null +++ b/bin/qunit-browser.js @@ -0,0 +1,216 @@ +/** + * usage: qunit-browser [--browser ] [...] + * + * --browser dotless = comma-separated names + * "./" file = JS or JSON file that returns an array + * Default: firefox (all are headless, open the file yourself for non-headless) + * Options: + * - firefox + * - chrome (chrome+chromium+edge) + * - chromium (chromium+chrome+edge) + * - edge (edge+chrome+chromium) + * - safari + * - browserstack/firefox_45 + * - browserstack/firefox_previous + * - browserstack/firefox_current, + * - ["browserstack", { + * "browser": "opera", + * "browser_version": "36.0", + * "device": null, + * "os": "OS X", + * "os_version": "Sierra" + * ] + * - saucelabs + * - puppeteer + * - puppeteer_coverage { outputDir: instanbul } + * // TODO: integration test with nyc as example with console+html output + * + * --file file served from file:///, expanded for you, or URL + * + * --concurrency=Infinity Always on? Responsibility of OS for sytem browsers + * to manage resources and figure it out, most cases will have 1 file and 1-3 browsers. + * likely reasons to want to limit it: + * - test file served from an app that cannot handle ANY concurrency. + * solution: run this one at a time in a loop consequtively with similar params. + * - using a cloud browser like browserstack or saucelabs and wanting to test N + * browsers but may only launch } */ + this.#cachedTestFile = this.#fetchTestFile(testFileUrl); + this.#browsers = new Map(); + this.#nextBrowserId = 1; + this.#proxyBase = null; + this.#reporter = reporter; + + http.on('listening', (port) => { + this.#proxyBase = 'http://localhost:' + port; + }); + http.on('request', '/:browserId', async (resp) => { + const browserId = req.url.path.split('/').last(); + resp.header(200); + resp.write(this.#getTestFile(testFileUrl, browserId)); + resp.end(); + // TODO: Report that browser launched and connected + }); + http.on('request', '/tap/:browserId', async (req) => { + const browserId = req.url.path.split('/').last(); + // TODO: Feed to TAP reporter. + // TODO: Verbose mode? + // Default: TAP where each browser is 1 virtual test in case of success. + // Verbose: TAP forwarded, test names prepended with [browsername]. + // Failures are shown either way, with prepended names. + // TODO: On "runEnd", report runtime + // Default: No-op, as overall TAP line as single test (above) can contain runtime + // Verbose: Output comment indicatinh browser done, and test runtime. + // TODO: On "runEnd" call browser.stop(); + }); + http.on('request', '/stop/:browserId', async (req) => { + const browserId = req.url.path.split('/').last(); + this.#browsers[browserId]?.stop(); + this.#browsers[browserId] = null; + }); + + // Start setting up the server in the background + http.startServer('randomPort'); + } + + async #fetchTestFile (url) { + // TODO: Does this support both file and HTTP? + const resp = await fetch(url); + return await resp.text(); + } + + async #getTestFile (testFileUrl, browserId) { + const html = await this.#cachedTestFile; + const reportUrl + // TODO: Inject element, unless there is one already + // in which case either inject nothing or ensure it doesn't have + // precedence. If injecting nothing, make sure there really is + // nothing between and the first base tag that might need one. + html.replace('', testFileUrl); + html.replace('