Open t2ym opened 7 years ago
https://github.com/Polymer/web-component-tester/issues/61 code coverage for both js and html sources
https://github.com/Polymer/web-component-tester/issues/478 Question: Why plugin middleware is never getting called
https://github.com/thedeeno/web-component-tester-istanbul/issues/38 Only show 100%, not listing the coverage of each file.
git diff master src
diff --git a/src/cli.ts b/src/cli.ts
index 08623bf..c25c1cd 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -65,6 +65,7 @@ export async function run(): Promise<StartServerResult> {
certPath: cliOptions['cert'],
pushManifestPath: cliOptions['manifest'],
proxy: proxyArgs.path && proxyArgs.target && proxyArgs,
+ wctPlugin: false
};
if (cliOptions.help) {
diff --git a/src/start_server.ts b/src/start_server.ts
index 1e70202..e7c4f0d 100644
--- a/src/start_server.ts
+++ b/src/start_server.ts
@@ -26,6 +26,9 @@ import * as url from 'url';
import {babelCompile} from './compile-middleware';
import {getComponentDir, getPackageName} from './config';
import {injectCustomElementsEs5Adapter} from './custom-elements-es5-adapter-middleware';
+import {Request, Response} from 'express';
+import {parse as parseContentType} from 'content-type';
+import {transformResponse} from './transform-middleware';
import {makeApp} from './make_app';
import {openBrowser} from './util/open_browser';
import {getPushManifest, pushResources} from './util/push';
@@ -100,6 +103,9 @@ export interface ServerOptions {
/** An optional list of routes & route handlers to attach to the polyserve
* app, to be handled before all others */
additionalRoutes?: Map<string, express.RequestHandler>;
+
+ /** Allow Plug-in for WCT */
+ wctPlugin?: boolean
}
function applyDefaultServerOptions(options: ServerOptions) {
@@ -362,36 +368,58 @@ export function getApp(options: ServerOptions): express.Express {
app.use(`/${escapedPath}/`, apiProxy);
}
- const forceCompile = options.compile === 'always';
- if (options.compile === 'auto' || forceCompile) {
- app.use('*', injectCustomElementsEs5Adapter(forceCompile));
- app.use('*', babelCompile(forceCompile));
+ app['_delayedAppConfig'] = () => {
+
+ const forceCompile = options.compile === 'always';
+ if (options.compile === 'auto' || forceCompile) {
+ app.use('*', injectCustomElementsEs5Adapter(forceCompile));
+ app.use('*', babelCompile(forceCompile));
+ }
+
+ app.use('*', transformResponse({
+ shouldTransform(request: Request, response: Response): boolean {
+ const contentTypeHeader = response.getHeader('Content-Type');
+ const contentType = contentTypeHeader && parseContentType(contentTypeHeader).type;
+ return !!(request && contentType && contentType && contentType.match(/^(text\/|application\/javascript)/));
+ },
+ transform(request: Request, response: Response, body: string): string {
+ return request && response ? body : '';
+ },
+ last: true
+ }));
+
+ app.use(`/${componentUrl}/`, polyserve);
+
+ // `send` expects files to be specified relative to the given root and as a
+ // URL rather than a file system path.
+ const entrypoint =
+ options.entrypoint ? urlFromPath(root, options.entrypoint) : 'index.html';
+
+ app.get('/*', (req, res) => {
+ pushResources(options, req, res);
+ const filePath = req.path;
+ send(req, filePath, {root: root, index: entrypoint})
+ .on('error',
+ (error: send.SendError) => {
+ if ((error).status === 404 && !filePathRegex.test(filePath)) {
+ // The static file handling middleware failed to find a file on
+ // disk. Serve the entry point HTML file instead of a 404.
+ send(req, entrypoint, {root: root}).pipe(res);
+ } else {
+ res.statusCode = error.status || 500;
+ res.end(error.message);
+ }
+ })
+ .pipe(res);
+ });
+
+ app['_delayedAppConfig'] = () => {};
+ }
+
+ if (!options.wctPlugin) {
+ app['_delayedAppConfig']();
}
- app.use(`/${componentUrl}/`, polyserve);
-
- // `send` expects files to be specified relative to the given root and as a
- // URL rather than a file system path.
- const entrypoint =
- options.entrypoint ? urlFromPath(root, options.entrypoint) : 'index.html';
-
- app.get('/*', (req, res) => {
- pushResources(options, req, res);
- const filePath = req.path;
- send(req, filePath, {root: root, index: entrypoint})
- .on('error',
- (error: send.SendError) => {
- if ((error).status === 404 && !filePathRegex.test(filePath)) {
- // The static file handling middleware failed to find a file on
- // disk. Serve the entry point HTML file instead of a 404.
- send(req, entrypoint, {root: root}).pipe(res);
- } else {
- res.statusCode = error.status || 500;
- res.end(error.message);
- }
- })
- .pipe(res);
- });
return app;
}
diff --git a/src/transform-middleware.ts b/src/transform-middleware.ts
index a08a42e..21efda4 100644
--- a/src/transform-middleware.ts
+++ b/src/transform-middleware.ts
@@ -16,7 +16,7 @@ import {Request, RequestHandler, Response} from 'express';
export function transformResponse(transformer: ResponseTransformer):
RequestHandler {
- return (req: Request, res: Response, next: () => void) => {
+ return transformer.last ? (req: Request, res: Response, next: () => void) => {
let ended = false;
const chunks: Buffer[] = [];
@@ -73,7 +73,15 @@ export function transformResponse(transformer: ResponseTransformer):
const body = Buffer.concat(chunks).toString('utf8');
let newBody = body;
try {
- newBody = transformer.transform(req, res, body);
+ let tmpBody = body;
+ if (Array.isArray(req['_transformers'])) {
+ req['_transformers'].forEach((_transformer: ResponseTransformer) => {
+ if (_transformer.shouldTransform(req, res)) {
+ tmpBody = _transformer.transform(req, res, tmpBody);
+ }
+ });
+ }
+ newBody = transformer.transform(req, res, tmpBody);
} catch (e) {
console.warn('Error', e);
}
@@ -89,6 +97,13 @@ export function transformResponse(transformer: ResponseTransformer):
};
next();
+ } :
+ (req: Request, res: Response, next: () => void) => {
+ if (req && res) {
+ req['_transformers'] = req['_transformers'] || [];
+ req['_transformers'].push(transformer);
+ }
+ next();
};
}
@@ -100,4 +115,6 @@ export interface ResponseTransformer {
shouldTransform(request: Request, response: Response): boolean;
transform(request: Request, response: Response, body: string): string;
+
+ last?: boolean;
}
git diff master browser runner/*[^d].ts
diff --git a/browser/clisocket.js b/browser/clisocket.js
index c55526b..d53c203 100644
--- a/browser/clisocket.js
+++ b/browser/clisocket.js
@@ -29,6 +29,7 @@ export default function CLISocket(browserId, socket) {
* interesting events back to the CLI runner.
*/
CLISocket.prototype.observe = function observe(runner) {
+ this.writable = true;
this.emitEvent('browser-start', {
url: window.location.toString(),
});
@@ -77,11 +78,91 @@ CLISocket.prototype.observe = function observe(runner) {
* @param {*} data Additional data to pass with the event.
*/
CLISocket.prototype.emitEvent = function emitEvent(event, data) {
- this.socket.emit('client-event', {
- browserId: this.browserId,
- event: event,
- data: data,
- });
+ var self = this;
+ var isPolling = false;
+ var dataJSON = data ? JSON.stringify(data) : '';
+ var dataSize = dataJSON.length;
+ var chunkSize = 65536;
+ var chunks = [];
+ var chunk;
+ var eventId;
+ var transport;
+ if (this.socket.io &&
+ this.socket.io.engine &&
+ this.socket.io.engine.transport &&
+ this.socket.io.engine.transport.query &&
+ this.socket.io.engine.transport.query.transport === 'polling') {
+ isPolling = true;
+ this.eventQueue = this.eventQueue || [];
+ }
+ if (isPolling) {
+ eventId = this.browserId + ',' + event + ',' + Date.now();
+ if (dataSize > chunkSize) {
+ transport = this.socket.io.engine.transport;
+ while (dataJSON) {
+ chunks.push(dataJSON.substr(0, chunkSize));
+ dataJSON = dataJSON.substr(chunkSize);
+ }
+ function processEventQueue() {
+ var eventItem;
+ while (eventItem = self.eventQueue.shift()) {
+ self.socket.emit(eventItem.event, eventItem.data);
+ if (eventItem.event === 'client-event-fragment') {
+ break;
+ }
+ }
+ if (self.eventQueue.length === 0) {
+ self.writable = true;
+ transport.removeListener('drain', processEventQueue);
+ }
+ }
+ while (chunk = chunks.shift()) {
+ this.eventQueue.push({
+ event: 'client-event-fragment',
+ data: {
+ browserId: self.browserId,
+ eventId: eventId,
+ event: event,
+ chunk: chunk,
+ last: chunks.length === 0
+ }
+ });
+ }
+ if (this.writable) {
+ this.writable = false;
+ transport.on('drain', processEventQueue);
+ if (transport.writable) {
+ processEventQueue();
+ }
+ }
+ }
+ else {
+ if (this.writable) {
+ this.socket.emit('client-event', {
+ browserId: this.browserId,
+ event: event,
+ data: data,
+ });
+ }
+ else {
+ this.eventQueue.push({
+ event: 'client-event',
+ data: {
+ browserId: this.browserId,
+ event: event,
+ data: data,
+ }
+ });
+ }
+ }
+ }
+ else {
+ this.socket.emit('client-event', {
+ browserId: this.browserId,
+ event: event,
+ data: data,
+ });
+ }
};
/**
diff --git a/browser/suites.js b/browser/suites.js
index a18409c..605d954 100644
--- a/browser/suites.js
+++ b/browser/suites.js
@@ -30,7 +30,7 @@ export function loadSuites(files) {
files.forEach(function(file) {
if (/\.js(\?.*)?$/.test(file)) {
jsSuites.push(file);
- } else if (/\.html(\?.*)?$/.test(file)) {
+ } else if (/\.html(\?.*)?(#.*)?$/.test(file)) {
htmlSuites.push(file);
} else {
throw new Error('Unknown resource type: ' + file);
diff --git a/runner/steps.ts b/runner/steps.ts
index b4899a5..8a46608 100644
--- a/runner/steps.ts
+++ b/runner/steps.ts
@@ -88,6 +88,7 @@ export async function runTests(context: Context): Promise<void> {
context._socketIOServers = context._httpServers.map((httpServer) => {
const socketIOServer = socketIO(httpServer);
socketIOServer.on('connection', function(socket) {
+ let fragmentBuffer = {};
context.emit('log:debug', 'Test client opened sideband socket');
socket.on('client-event', function(data: ClientMessage<any>) {
const runner = runners[data.browserId];
@@ -98,6 +99,21 @@ export async function runTests(context: Context): Promise<void> {
}
runner.onEvent(data.event, data.data);
});
+ socket.on('client-event-fragment', function (data) {
+ const runner = runners[data.browserId];
+ if (!runner) {
+ throw new Error(
+ `Unable to find browser runner for ` +
+ `browser with id: ${data.browserId}`);
+ }
+ fragmentBuffer[data.eventId] = fragmentBuffer[data.eventId] || [];
+ fragmentBuffer[data.eventId].push(data.chunk);
+ if (data.last) {
+ data.data = JSON.parse(fragmentBuffer[data.eventId].join(''));
+ delete fragmentBuffer[data.eventId];
+ runner.onEvent(data.event, data.data);
+ }
+ });
});
return socketIOServer;
});
diff --git a/runner/webserver.ts b/runner/webserver.ts
index 98d6061..7758272 100644
--- a/runner/webserver.ts
+++ b/runner/webserver.ts
@@ -134,6 +134,7 @@ Expected to find a ${mdFilenames.join(' or ')} at: ${pathToLocalWct}/
// Serve up project & dependencies via polyserve
const polyserveResult = await startServers({
+ wctPlugin: true,
root: options.root,
compile: options.compile,
hostname: options.webserver.hostname,
@@ -176,6 +177,7 @@ Expected to find a ${mdFilenames.join(' or ')} at: ${pathToLocalWct}/
// webservers as they please.
for (const server of servers) {
await wct.emitHook('prepare:webserver', server.app);
+ server.app['_delayedAppConfig']();
}
options.webserver._servers = servers.map(s => {
server.app['_delayedAppConfig']()
be refined as server.delayedAppConfig()
with its type information?req['_transformers']
be stored and handed more elegantly?last
property of transformers be renamed as isLastTransformer
?transport-middleware.ts
?writable
status) and client-event-fragment
events complete and robust?
emit()
calls just concatenate all the queued payloads in a SINGLE HTTP POST request, which would be blocked on SauceLabs squid proxy, the next drain
event of SocketIO polling transport has to trigger asynchronous emitting of a payload with the next 64KB chunk.iron-location
query parameter disappearance issue.polyserve/lib/transform-middleware
The patched version is available on NPM repository.
https://www.npmjs.com/package/@t2ym/web-component-tester
npm install --save-dev @t2ym/web-component-tester
npm install --save-dev wct-istanbul
bower install --save-dev t2ym/web-component-tester#^6.0.1
It seems the main repository is undergoing major changes toward Polymer 3.0 with full NPM support. It has to be considered that this patching is conceptually compatible with the coming versions.
Create pull requests to merge the patches into the official web-component-tester
ToDos