Skip to content

Commit

Permalink
first version based on playwright
Browse files Browse the repository at this point in the history
support for coverage will follow after this istanbuljs/v8-to-istanbul#132 is solved
  • Loading branch information
hkollmann committed Feb 3, 2021
1 parent a5608ce commit fe5acb5
Show file tree
Hide file tree
Showing 13 changed files with 188 additions and 9,715 deletions.
8 changes: 2 additions & 6 deletions .github/workflows/unit-tests.yml
Expand Up @@ -15,15 +15,11 @@ jobs:
strategy:
matrix:
os:
- ubuntu-18.04
# Alien::MSYS fails to install on windows
# - windows-latest
- ubuntu-latest

node:
- '8.x'
- '10.x'
- '11.x'
- '13.x'
- '12.x'

fail-fast: false

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -10,3 +10,4 @@

# IDEs
.idea/
/.vscode
1 change: 0 additions & 1 deletion .nvmrc

This file was deleted.

18 changes: 0 additions & 18 deletions .travis.yml-offline

This file was deleted.

9 changes: 6 additions & 3 deletions Manifest.json
Expand Up @@ -14,9 +14,13 @@
{
"name": "Tobias Oetiker (oetiker)",
"email": "tobi@oetiker.ch"
},
{
"name": "Henner Kollmann (hkollmann)",
"email": "Henner.Kollmann@gmx.de"
}
],
"version": "0.5.4",
"version": "1.0.0",
"sourceViewUri": "https://github.com/qooxdoo/qxl.testtapper/blob/%{qxGitBranch}/source/class/%{classFilePath}#L%{lineNumber}"
},
"provides": {
Expand All @@ -42,7 +46,6 @@
},
"requires": {
"@qooxdoo/compiler": "^1.0.0-beta",
"@qooxdoo/framework": "^6.0.0-alpha",
"qooxdoo/qxl.logpane": "^1.0.0-beta.3"
"@qooxdoo/framework": "^6.0.0-alpha"
}
}
6 changes: 1 addition & 5 deletions README.md
Expand Up @@ -14,8 +14,6 @@ http://www.qooxdoo.org/qxl.testtapper/
```
$ npx qx package update
$ npx qx package install qooxdoo/qxl.testtapper
$ echo 10 > .nvmrc
$ npm i puppeteer yargs nyc coveralls puppeteer-to-istanbul
```

Now edit the `"myapp.test.*"` entry in your `compile.json` file to point to the
Expand All @@ -30,11 +28,9 @@ Then browse to [http://localhost:8080](http://localhost:8080). You will see tha
If you want to run the tests from the command line you need a headless browser to run the tests. TestTAPper uses the puppeteer node module which comes with a built in copy of headless chrome and is thus very simple to use

```
$ node compile/source/resource/qxl/testtapper/run.js http://localhost:8080
$ node_modules/.bin/nyc report --reporter=text-lcov | node_modules/.bin/coveralls
$ npx qx test
```

if you start `run.js` without arguments it ouputs a little help

## Developing testTAPper
Clone this repo and compile it:
Expand Down
235 changes: 163 additions & 72 deletions compile.js
@@ -1,10 +1,15 @@

const { promisify } = require('util');
const fs = require('fs');
const writeFile = promisify(fs.writeFile);
const mkdir = promisify(fs.mkdir);
const path = require('path');
const { URL } = require("url");

qx.Class.define("qxl.testtapper.compile.LibraryApi", {
extend: qx.tool.cli.api.LibraryApi,

members: {

// @overridden
async initialize() {
let yargs = qx.tool.cli.commands.Test.getYargsCommand;
Expand All @@ -20,103 +25,184 @@ qx.Class.define("qxl.testtapper.compile.LibraryApi", {
};
args.builder.diag = {
describe: "show diagnostic output",
type: "boolean"
type: "boolean",
default: false
};
args.builder.terse = {
describe: "show only summary and errors",
type: "boolean",
default: false
};
/*
args.builder.coverage = {
describe: "writes coverage infos, only working for chromium yet",
type: "boolean",
default: false
};
*/
args.builder.headless = {
describe: "runs test headless",
type: "boolean"
};
args.builder.browsers = {
describe: "writes coverage infos, only working for chromium yet",
type: "string"
};
return args;
}
},

__enviroment: null,
// @overridden
load: async function() {
load: async function () {
let command = this.getCompilerApi().getCommand();
if (command instanceof qx.tool.cli.commands.Test) {
command.addListener("runTests", this.__onRunTests, this);
if (command.setNeedsServer) {
command.addListener("runTests", this.__testIt, this);
if (command.setNeedsServer) {
command.setNeedsServer(true);
}
}
},

__onRunTests: function(data) {
let app = this.getTestApp("qxl.testtapper.Application");
__testIt: function (data) {
let app = this.__getTestApp("qxl.testtapper.Application");
if (!app) {
qx.tool.compiler.Console.print("Please install testtapper application in compile.json");
qx.tool.compiler.Console.error("Please install testtapper application in compile.json");
return qx.Promise.resolve(false);
}
let result = data.getData?data.getData():{};
return this.runTest(app, result);
let result = data.getData();
return this.__runTests(app, result);
},

runTest : async function (app, result) {
return new qx.Promise(async (resolve) => {
const puppeteer = this.require("puppeteer");
const pti = this.require("puppeteer-to-istanbul");
let outputDir = "";
if (this.getCompilerApi().getCommand().showStartpage()) {
let target = app.maker.getTarget();
outputDir = target.getOutputDir();
}
let href = `http://localhost:${app.port}/${outputDir}${app.name}/`;
const { URL } = require("url");
let url = new URL(href);
let s = ""
if (app.argv.method) {
s += "method=" + app.argv.method;
}
if (app.argv.class) {
if (s.length > 0) {
s += "&";

__runTestInBrowser(browserType, url, app, result) {
return new qx.Promise(async (resolve, reject) => {
try {
const playwright = this.require('playwright');
const v8toIstanbul = this.require('v8-to-istanbul');
console.info("TESTTAPPER: Running test in " + browserType);
const launchArgs = {
args: [
'--no-sandbox',
'--disable-setuid-sandbox'
],
headless: app.argv.headless ?? app.environment["qxl.testtapper.headless"] ?? true
};
const browser = playwright[browserType];
if (!browser) {
reject(new Error(`unknown browser ${browserType}`));
}
s += "class=" + app.argv.class;
}
if (s.length > 0) {
url.search = s;
}
qx.tool.compiler.Console.log("CALL "+ url.href);
let notOk = 0;
let Ok = 0;
let browser = await puppeteer.launch({args: ['--no-sandbox']});
let page = await browser.newPage();
page.on("console", async (msg) => {
let val = msg.text();
// value is serializable
if (val.match(/^\d+\.\.\d+$/)) {
qx.tool.compiler.Console.info(`DONE testing ${Ok} ok, ${notOk} not ok`);
let jsCoverage = await page.coverage.stopJSCoverage();
qx.tool.compiler.Console.info("writing coverage information ...");
await pti.write(jsCoverage);
await browser.close();
result[app.name] = {
notOk: notOk,
ok: Ok
};
result.setExitCode(notOk);
resolve(result[app.name]);
} else if (val.match(/^not ok /)) {
notOk++;
qx.tool.compiler.Console.log(val);
result.setExitCode(notOk);
} else if (val.match(/^ok\s/)) {
Ok++;
if (!app.argv.terse) {
const context = await browser.launch(launchArgs);
const page = await context.newPage();
let cov = (app.argv.coverage ?? app.environment["qxl.testtapper.coverage"] ?? false) && (browserType === "chromium");
if (cov) {
await page.coverage.startJSCoverage();
}
let Ok = 0;
let notOk = 0;
page.on("console", async (msg) => {
let val = msg.text();
// value is serializable
if (val.match(/^\d+\.\.\d+$/)) {
qx.tool.compiler.Console.info(`DONE testing ${Ok} ok, ${notOk} not ok`);
if (cov) {
qx.tool.compiler.Console.info("writing coverage information ...");
const coverage = await page.coverage.stopJSCoverage();
const entries = {};
let target = app.maker.getTarget();
let outputDir = target.getOutputDir();
const sourceMapUrl = this.require("source-map-url");
for await (entry of coverage) {
let source;
let sm = sourceMapUrl.getFrom(entry.source);
if (sm) {
sm = sm.split("?")[0];
source = sourceMapUrl.removeFrom(entry.source);
source += `//# sourceMappingURL=${sm}`;
} else {
source = entry.source;
}
let url = new URL(entry.url);
filePath = path.join(process.cwd(), outputDir, url.pathname);
const converter = new v8toIstanbul(filePath, 0, {
source: source
});
await converter.load();
converter.applyCoverage(entry.functions);
const instanbul = converter.toIstanbul();
entries[filePath] = instanbul[filePath];
}
await mkdir(path.join(process.cwd(), '.nyc_output'), { recursive: true });
await writeFile(path.join(process.cwd(), '.nyc_output', 'out.json'), JSON.stringify(entries));
}
await context.close();
result[app.name][browserType] = {
notOk: notOk,
ok: Ok
};
result.setExitCode(result.getExitCode() + notOk);
resolve();
} else if (val.match(/^not ok /)) {
notOk++;
qx.tool.compiler.Console.log(val);
} else if (val.match(/^ok\s/)) {
Ok++;
if (!app.argv.terse) {
qx.tool.compiler.Console.log(val);
}
} else if (val.match(/^#/) && app.argv.diag) {
qx.tool.compiler.Console.log(val);
} else if (app.argv.verbose) {
qx.tool.compiler.Console.log(val);
}
} else if (val.match(/^#/) && app.argv.diag) {
qx.tool.compiler.Console.log(val);
} else if (app.argv.verbose) {
qx.tool.compiler.Console.log(val);
}
});
await page.coverage.startJSCoverage();
await page.goto(url.href);
});
page.goto(url.href);
} catch (e) {
reject(e);
}
});
},

getTestApp: function(classname) {
__runTests: async function (app, result) {
let outputDir = "";
if (this.getCompilerApi().getCommand().showStartpage()) {
let target = app.maker.getTarget();
outputDir = target.getOutputDir();
}
let href = `http://localhost:${app.port}/${outputDir}${app.name}/`;
let url = new URL(href);
let s = "";
if (app.argv.method) {
s += "method=" + app.argv.method;
}
if (app.argv.class) {
if (s.length > 0) {
s += "&";
}
s += "class=" + app.argv.class;
}
if (s.length > 0) {
url.search = s;
}
qx.tool.compiler.Console.log("CALL " + url.href);
result[app.name] = {};
let browsers = (app.argv.browsers || "").split(",").filter(s => s.length > 0);
if (browsers.length === 0) {
browsers = app.environment["qxl.testtapper.browsers"];
}
if (!browsers || browsers.length === 0) {
browsers = ["chromium"];
}
for (const browserType of browsers) {
try {
await this.__runTestInBrowser(browserType, url, app, result);
} catch (e) {
qx.tool.compiler.Console.error(e);
}
}
},

__getTestApp: function (classname) {
let command = this.getCompilerApi().getCommand();
let maker = null;
let app = null;
Expand All @@ -139,15 +225,20 @@ qx.Class.define("qxl.testtapper.compile.LibraryApi", {
qx.tool.compiler.Console.print("qx.tool.cli.test.noAppName");
return null;
}
let env = app.getEnvironment();
if (env["testtapper.testNameSpace"]) {
qx.tool.compiler.Console.error('environment["testtapper.testNameSpace"] is deprecated, use environment["qxl.testtapper.testNameSpace"] instead');
env["qxl.testtapper.testNameSpace"] = env["testtapper.testNameSpace"];
}
var config = command._getConfig();
return {
name: app.getName(),
environment: env,
port: config.serve.listenPort,
argv: command.argv,
maker: maker
}
}

}
});

Expand Down

0 comments on commit fe5acb5

Please sign in to comment.