puppeteer to testcafe

pull/5364/head
Milan Hauth 5 years ago
parent c8c50b5ba8
commit 42485d0294

@ -78,6 +78,7 @@
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-svelte3": "^2.7.3",
"estree-walker": "^1.0.0",
"get-port": "^5.1.1",
"is-reference": "^1.1.4",
"jsdom": "^15.1.1",
"kleur": "^3.0.3",
@ -85,10 +86,10 @@
"magic-string": "^0.25.3",
"mocha": "^6.2.0",
"periscopic": "^2.0.1",
"puppeteer": "^2.1.1",
"rollup": "^1.27.14",
"source-map": "^0.7.3",
"source-map-support": "^0.5.13",
"testcafe": "^1.9.2",
"tiny-glob": "^0.2.6",
"tslib": "^1.10.0",
"typescript": "^3.5.3"

@ -3,59 +3,113 @@ import * as path from 'path';
import * as http from 'http';
import { rollup } from 'rollup';
import * as virtual from '@rollup/plugin-virtual';
import * as puppeteer from 'puppeteer';
import { TestcafeController, testcafeHolder } from '../testcafeController.js';
import * as getPort from 'get-port';
import { platform as os_platform } from 'os';
import { execSync } from 'child_process';
import { addLineNumbers, loadConfig, loadSvelte } from "../helpers.js";
import { deepEqual } from 'assert';
const page = `
<body>
<main></main>
<script src='/bundle.js'></script>
</body>
const pageHtml = `\
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Svelte Test</title>
</head>
<body>
<main></main>
<script src="/bundle.js"></script>
</body>
</html>
`;
const assert = fs.readFileSync(`${__dirname}/assert.js`, 'utf-8');
describe('custom-elements', function() {
this.timeout(10000);
const testGroup = 'custom-elements';
describe(testGroup, async function(env) {
this.timeout(20000);
let svelte;
let server;
let browser;
let serverPort;
let testcafe;
let controller;
let code;
function create_server() {
return new Promise((fulfil, reject) => {
const server = http.createServer((req, res) => {
if (req.url === '/') {
res.end(page);
}
if (req.url === '/bundle.js') {
res.end(code);
}
if (req.url == '/') res.end(pageHtml);
if (req.url == '/bundle.js') res.end(code);
});
server.on('error', reject);
server.listen('6789', () => {
server.listen(serverPort, () => {
fulfil(server);
});
});
}
before(async () => {
this.timeout(10000);
svelte = loadSvelte();
console.log('[custom-element] Loaded Svelte');
console.log(' i Loaded Svelte');
serverPort = await getPort();
server = await create_server();
console.log('[custom-element] Started server');
browser = await puppeteer.launch();
console.log('[custom-element] Launched puppeteer browser');
console.log(` i Started server at http://localhost:${serverPort}/`);
// init testcafe
// write tempfile
TestcafeController.createTestFile({
fixtureName: testGroup,
});
// guess browser
const testcafeBrowserList = await TestcafeController.testcafeListLocalBrowsers();
// browsers sorted by personal taste
const preferredBrowsers = [
'chrome', 'chromium', 'chrome-canary',
'firefox', 'safari', 'opera', 'edge', 'ie',
];
// get default browser name: harder on windows, crazy hard on darwin
// https://github.com/jakub-g/x-default-browser
const userDefaultBrowser = ['linux', 'freebsd'].includes(os_platform())
? execSync('xdg-mime query default x-scheme-handler/http').toString()
.replace(/.*(firefox|chrome|chromium|opera).*\n/i, '$1').toLowerCase()
: null;
const browser = testcafeBrowserList.includes(userDefaultBrowser)
? userDefaultBrowser
: preferredBrowsers.find( // find first match
b => testcafeBrowserList.includes(b)
) || testcafeBrowserList[0];
const HL = browser.match(/(firefox|chro)/) ? ':headless' : '';
// find open ports
const port1 = await getPort();
const port2 = await getPort();
console.log(' i Starting TestCafe in browser '+browser+HL);
testcafe = await TestcafeController.createTestCafeWithRunner(
[ browser+HL ], port1, port2,
);
console.log(' i Getting TestCafe controller');
// this will print 'Running tests in: ....'
const testController = await testcafeHolder.get();
controller = new TestcafeController(testController);
console.log(' i init done');
});
after(async () => {
if (server) server.close();
if (browser) await browser.close();
if (testcafe) await testcafe.close();
TestcafeController.deleteTestFile();
});
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
@ -105,29 +159,60 @@ describe('custom-elements', function() {
]
});
const result = await bundle.generate({ format: 'iife', name: 'test' });
code = result.output[0].code;
const generated = await bundle.generate({ format: 'iife', name: 'test' });
code = generated.output[0].code;
const page = await browser.newPage();
try {
//const t1 = (new Date()).getTime();
// start loading test page
await controller.t.navigateTo('http://localhost:'+serverPort+'/');
await controller.t.wait(50); // TODO better?
// wait for page load: retry loop
// takes between 500 and 1500 ms
let testFunc;
for (let i = 0; i < 100; i++) {
try {
testFunc = controller.getFunc(
() => (
test(document.querySelector('main'))
)
);
break;
}
catch (e) {
// script not yet loaded
// assert: e.errMsg == "ReferenceError: test is not defined"
await controller.t.wait(50);
process.stdout.write('.');
}
}
page.on('console', (type, ...args) => {
console[type](...args);
});
const testResult = await testFunc();
page.on('error', error => {
console.log('>>> an error happened');
console.error(error);
});
//const t2 = (new Date()).getTime();
//console.log(' '+(t2-t1)+' ms for page load');
try {
await page.goto('http://localhost:6789');
// print console messages
// in current version of testcafe
// the messages are not sorted by time (bug)
const testConsole = await controller.popConsole();
['error', 'log', 'info', 'warn'].forEach(key => {
if (testConsole[key].length > 0) {
console.log(key+': '+testConsole[key].join('\n'+key+': '));
}
})
const result = await page.evaluate(() => test(document.querySelector('main')));
if (result) console.log(result);
} catch (err) {
if (testResult) console.log(testResult);
}
catch (err) {
console.log(addLineNumbers(code));
throw err;
} finally {
// testcafe error objects are verbose and useless
throw err.errMsg ? new Error(err.errMsg) : err;
}
finally {
if (expected_warnings) {
deepEqual(warnings.map(w => ({
code: w.code,

@ -0,0 +1,163 @@
// based on https://github.com/cvializ/testcafe-mocha
import * as fs from 'fs';
import * as createTestCafe from 'testcafe';
import { ClientFunction, Selector, TestController } from 'testcafe';
import * as browserProviderPool from 'testcafe/lib/browser/provider/pool';
export const testcafeHolder = {
testController: null,
captureResolver: null,
getResolver: null,
capture: function(t) {
testcafeHolder.testController = t;
if (testcafeHolder.getResolver) {
testcafeHolder.getResolver(t);
}
return new Promise(function(resolve) {
testcafeHolder.captureResolver = resolve;
});
},
free: function() {
testcafeHolder.testController = null;
if (testcafeHolder.captureResolver) {
testcafeHolder.captureResolver();
}
},
get: function() {
return new Promise(function(resolve) {
if (testcafeHolder.testController) {
resolve(testcafeHolder.testController);
} else {
testcafeHolder.getResolver = resolve;
}
});
},
};
export class TestcafeController {
// path is relative to project root
// must not contain slashes
static testcafeTempfile = 'temp_testcafeTestSrc.js';
static createTestCafeWithRunner(browserList, port1, port2) {
return createTestCafe('localhost', port1, port2)
.then(function(testcafe) {
const runner = testcafe.createRunner();
// http://devexpress.github.io/testcafe/documentation/using-testcafe/programming-interface/runner.html
runner
.src('./'+TestcafeController.testcafeTempfile)
//.screenshots('reports/screenshots/', true)
.browsers(browserList)
.run();
return testcafe;
});
}
// TODO better than tempfile?
// add method to testcafe.runner?
static createTestFile(options) {
const opt = Object.assign({}, {
// default options
fixtureName: 'fixture',
testName: 'test',
// import path is relative to project root
testcafeControllerFile: './test/testcafeController',
}, options);
fs.writeFileSync(
TestcafeController.testcafeTempfile,
`
import { testcafeHolder } from ${JSON.stringify(opt.testcafeControllerFile)};
fixture(${JSON.stringify(opt.fixtureName)});
test(${JSON.stringify(opt.testName)}, testcafeHolder.capture);
`
);
}
static deleteTestFile() {
fs.unlinkSync(TestcafeController.testcafeTempfile);
}
static async testcafeListLocalBrowsers() {
// copy paste from testcafe/lib/cli/cli.js
const providerName = 'locally-installed';
const provider = await browserProviderPool.getProvider(providerName);
if (provider && provider.isMultiBrowser) {
const browserNames = await provider.getBrowserList();
await browserProviderPool.dispose();
return browserNames;
}
else
return [];
}
constructor(t) {
this.t = t; // testController
this._fn_getTitle = this.getFunc(() => document.title);
this.consolePos = null;
}
bindFunc(fn) {
return fn.with({boundTestRun: this.t});
}
// run javascript in browser. sample use:
// const f = c.getFunc(() => document.title);
// const t = await f();
getFunc(fn) {
return this.bindFunc(ClientFunction(fn));
}
// some utility functions
async findFirstElement(cssSelector) {
return await this.bindFunc(Selector(cssSelector).nth(0));
}
// send keyboard string to browser
// handle null = document
async sendKeys(handle, keys) {
if (handle)
await this.t.typeText(handle.getElement(), keys);
else
await this.t.pressKey(keys);
}
// get last console messages
async popConsole() {
const _console = await this.t.getBrowserConsoleMessages();
const res = this.consolePos
? Object.keys(_console)
.reduce((acc, key) => {
acc[key] = _console[key].slice(this.consolePos[key]);
return acc;
}, {})
: _console;
// save array positions
this.consolePos = Object.keys(_console)
.reduce((acc, key) => {
acc[key] = _console[key].length;
return acc;
}, {});
return res;
}
async getElementText(handle) {
return await handle.getElement().innerText;
}
async getTitle() {
return await this._fn_getTitle();
}
async click(handle) {
return await this.t.click(handle.getElement());
}
}
Loading…
Cancel
Save