chore: migrate html reporter to vite (#13116)

This commit is contained in:
Pavel Feldman 2022-03-28 17:21:19 -08:00 committed by GitHub
parent d59c2b996f
commit 8758cf8cbf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 246 additions and 273 deletions

4
package-lock.json generated
View File

@ -7841,7 +7841,9 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"packages/html-reporter": {},
"packages/html-reporter": {
"version": "0.0.0"
},
"packages/playwright": {
"version": "1.21.0-next",
"hasInstallScript": true,

View File

@ -1 +1,24 @@
out/
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -0,0 +1,53 @@
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
import fs, { existsSync } from 'fs';
import path from 'path';
/**
* @returns {import('vite').Plugin}
*/
export function bundle() {
let config;
return {
name: 'playwright-bundle',
config(c) {
config = c;
},
transformIndexHtml: {
transform(html, ctx) {
if (!ctx || !ctx.bundle)
return html;
for (const [, value] of Object.entries(ctx.bundle)) {
if (value.code)
html = html.replace(/<script type="module".*<\/script>/, () => `<script type="module">${value.code}</script>`);
else
html = html.replace(/<link rel="stylesheet"[^>]*>/, () => `<style type='text/css'>${value.source}</style>`);
}
return html;
},
},
closeBundle: () => {
if (existsSync(path.join(config.build.outDir, 'index.html'))) {
const targetDir = path.join(__dirname, '..', 'playwright-core', 'lib', 'webpack', 'htmlReport');
fs.mkdirSync(targetDir, { recursive: true });
fs.copyFileSync(
path.join(config.build.outDir, 'index.html'),
path.join(targetDir, 'index.html'));
}
},
}
}

View File

@ -1,66 +0,0 @@
/*
Copyright (c) Microsoft Corporation.
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.
*/
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
class BundleJsPlugin {
constructor() {
}
apply(compiler) {
compiler.hooks.compilation.tap('bundle-js-plugin', compilation => {
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync('bundle-js-plugin', (htmlPluginData, callback) => {
callback(null, this.processTags(compilation, htmlPluginData));
});
});
};
processTags(compilation, pluginData) {
const headTags = pluginData.headTags.map(tag => this.processTag(compilation, tag));
const bodyTags = pluginData.bodyTags.map(tag => this.processTag(compilation, tag));
return { ...pluginData, headTags, bodyTags };
}
processTag(compilation, tag) {
if (tag.tagName !== 'script' || !tag.attributes.src)
return tag;
const asset = getAssetByName(compilation.assets, tag.attributes.src);
const innerHTML = asset.source().replace(/(<)(\/script>)/g, '\\x3C$2');
return {
tagName: 'script',
attributes: {
type: 'text/javascript'
},
closeTag: true,
innerHTML,
};
}
}
function getAssetByName (assets, assetName) {
for (var key in assets) {
if (assets.hasOwnProperty(key)) {
var processedKey = path.posix.relative('', key);
if (processedKey === assetName) {
return assets[key];
}
}
}
}
module.exports = BundleJsPlugin;

View File

@ -23,6 +23,7 @@
<title>Playwright Test Report</title>
</head>
<body>
<div id=root></div>
<div id='root'></div>
<script type='module' src='/src/index.tsx'></script>
</body>
</html>

View File

@ -1,4 +1,10 @@
{
"name": "html-reporter",
"private": true
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build && tsc",
"preview": "vite preview"
}
}

View File

@ -15,8 +15,6 @@
*/
import { PlaywrightTestConfig, devices } from '@playwright/test';
import path from 'path';
import url from 'url';
const config: PlaywrightTestConfig = {
testDir: 'src',
@ -27,8 +25,14 @@ const config: PlaywrightTestConfig = {
] : [
['html', { open: 'on-failure' }]
],
webServer: {
url: 'http://localhost:3101/tests.html',
command: 'npm run dev',
cwd: __dirname,
reuseExistingServer: !process.env.CI,
},
use: {
baseURL: url.pathToFileURL(path.join(__dirname, 'out', 'index.html')).toString(),
baseURL: 'http://localhost:3101/tests.html',
trace: 'on-first-retry',
},
projects: [

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
import type { TestCaseSummary } from '@playwright/test/src/reporters/html';
import type { TestCaseSummary } from '@playwright-test/reporters/html';
export class Filter {
project: string[] = [];

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
import type { Stats } from '@playwright/test/src/reporters/html';
import type { Stats } from '@playwright-test/reporters/html';
import * as React from 'react';
import './colors.css';
import './common.css';

View File

@ -14,13 +14,17 @@
* limitations under the License.
*/
import type { HTMLReport, TestAttachment } from '@playwright/test/src/reporters/html';
import type { HTMLReport, TestAttachment } from '@playwright-test/reporters/html';
import type zip from '@zip.js/zip.js';
// @ts-ignore
import zipImport from '@zip.js/zip.js/dist/zip-no-worker-inflate.min.js';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import './colors.css';
import { LoadedReport } from './loadedReport';
import { ReportView } from './reportView';
// @ts-ignore
const zipjs = zipImport as typeof zip;
export type Metadata = Partial<{
'generatedAt': number;
@ -75,8 +79,6 @@ const extractMetadata = (attachments: TestAttachment[]): Metadata | undefined =>
return out;
};
const zipjs = (self as any).zip;
const ReportLoader: React.FC = () => {
const [report, setReport] = React.useState<LoadedReport | undefined>();
React.useEffect(() => {
@ -97,7 +99,7 @@ class ZipReport implements LoadedReport {
private _json!: HTMLReport & { metadata?: Metadata };
async load() {
const zipReader = new zipjs.ZipReader(new zipjs.Data64URIReader(window.playwrightReportBase64), { useWebWorkers: false }) as zip.ZipReader;
const zipReader = new zipjs.ZipReader(new zipjs.Data64URIReader((window as any).playwrightReportBase64), { useWebWorkers: false }) as zip.ZipReader;
for (const entry of await zipReader.getEntries())
this._entries.set(entry.filename, entry);
this._json = await this.entry('report.json') as HTMLReport;

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
import type { TestAttachment } from '@playwright/test/src/reporters/html';
import type { TestAttachment } from '@playwright-test/reporters/html';
import * as React from 'react';
import * as icons from './icons';
import { TreeItem } from './treeItem';

View File

@ -14,8 +14,8 @@
* limitations under the License.
*/
import { HTMLReport } from '@playwright/test/src/reporters/html';
import { Metadata } from '.';
import { HTMLReport } from '@playwright-test/reporters/html';
import { Metadata } from './index';
export interface LoadedReport {
json(): HTMLReport & { metadata?: Metadata };

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
import type { TestCase, TestFile } from '@playwright/test/src/reporters/html';
import type { TestCase, TestFile } from '@playwright-test/reporters/html';
import * as React from 'react';
import './colors.css';
import './common.css';
@ -27,7 +27,7 @@ import { TestCaseView } from './testCaseView';
import { TestFilesView } from './testFilesView';
import './theme.css';
import * as icons from './icons';
import { Metadata } from '.';
import { Metadata } from './index';
declare global {
interface Window {

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
import type { TestCase } from '@playwright/test/src/reporters/html';
import type { TestCase } from '@playwright-test/reporters/html';
import * as React from 'react';
import { TabbedPane } from './tabbedPane';
import { AutoChip } from './chip';

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
import type { HTMLReport, TestFileSummary } from '@playwright/test/src/reporters/html';
import type { HTMLReport, TestFileSummary } from '@playwright-test/reporters/html';
import * as React from 'react';
import { msToString } from './uiUtils';
import { Chip } from './chip';

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
import type { HTMLReport, TestFileSummary } from '@playwright/test/src/reporters/html';
import type { HTMLReport, TestFileSummary } from '@playwright-test/reporters/html';
import * as React from 'react';
import { Filter } from './filter';
import { TestFileView } from './testFileView';

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
import type { TestAttachment, TestCase, TestResult, TestStep } from '@playwright/test/src/reporters/html';
import type { TestAttachment, TestCase, TestResult, TestStep } from '@playwright-test/reporters/html';
import ansi2html from 'ansi-to-html';
import * as React from 'react';
import { TreeItem } from './treeItem';

View File

@ -14,10 +14,10 @@
* limitations under the License.
*/
import { AutoChip, Chip } from '../src/chip';
import { HeaderView } from '../src/headerView';
import { TestCaseView } from '../src/testCaseView';
import '../src/theme.css';
import { AutoChip, Chip } from './chip';
import { HeaderView } from './headerView';
import { TestCaseView } from './testCaseView';
import './theme.css';
import register from '@playwright/experimental-ct-react/register';

View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@ -15,14 +15,15 @@
-->
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset='UTF-8'>
<meta name='color-scheme' content='dark light'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>Playwright CT</title>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tests</title>
</head>
<body>
<div id=root></div>
<div id="root"></div>
<script type="module" src="/src/tests.ts"></script>
</body>
</html>

View File

@ -1,62 +0,0 @@
/*
Copyright (c) Microsoft Corporation.
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.
*/
const path = require('path');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const mode = process.env.NODE_ENV === 'production' ? 'production' : 'development';
module.exports = {
mode,
entry: {
tests: path.join(__dirname, 'tests.ts'),
},
resolve: {
extensions: ['.ts', '.js', '.tsx', '.jsx']
},
devtool: mode === 'production' ? false : 'source-map',
output: {
globalObject: 'self',
filename: '[name].bundle.js',
path: path.resolve(__dirname, '..', 'out')
},
module: {
rules: [
{
test: /\.(j|t)sx?$/,
loader: 'babel-loader',
options: {
presets: [
"@babel/preset-typescript",
"@babel/preset-react"
]
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
]
},
plugins: [
new HtmlWebPackPlugin({
title: 'Playwright CT',
template: path.join(__dirname, 'tests.html'),
inject: true,
}),
]
};

View File

@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": true,
"skipLibCheck": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"baseUrl": ".",
"useUnknownInCatchVariables": false,
"paths": {
"@web/*": ["../web/src/*"],
"@playwright-core/*": ["../playwright-core/src/*"],
"@playwright-test/*": ["../playwright-test/src/*"],
"playwright-core/lib/*": ["../playwright-core/src/*"],
"playwright-test/lib/*": ["../playwright-test/src/*"],
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"composite": true,
"module": "esnext",
"moduleResolution": "node"
},
"include": ["vite.config.ts"]
}

View File

@ -0,0 +1,50 @@
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { bundle } from './bundle';
import * as path from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
bundle()
],
resolve: {
alias: {
'@web': path.resolve(__dirname, '../web/src'),
'@playwright-core': path.resolve(__dirname, '../playwright-core/src'),
},
},
build: {
outDir: path.resolve(__dirname, 'dist'),
emptyOutDir: true,
assetsInlineLimit: 100000000,
chunkSizeWarningLimit: 100000000,
cssCodeSplit: false,
rollupOptions: {
inlineDynamicImports: true,
output: {
manualChunks: undefined,
},
},
},
server: {
port: 3101,
},
});

View File

@ -1,65 +0,0 @@
/*
Copyright (c) Microsoft Corporation.
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.
*/
const path = require('path');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const BundleJsPlugin = require('./bundleJsPlugin');
const mode = process.env.NODE_ENV === 'production' ? 'production' : 'development';
module.exports = {
mode,
entry: {
zip: require.resolve('@zip.js/zip.js/dist/zip-no-worker-inflate.min.js'),
app: path.join(__dirname, 'src', 'index.tsx'),
},
resolve: {
extensions: ['.ts', '.js', '.tsx', '.jsx']
},
devtool: mode === 'production' ? false : 'source-map',
output: {
globalObject: 'self',
filename: '[name].bundle.js',
path: path.resolve(__dirname, '..', 'playwright-core', 'lib', 'webpack', 'htmlReport')
},
module: {
rules: [
{
test: /\.(j|t)sx?$/,
loader: 'babel-loader',
options: {
presets: [
"@babel/preset-typescript",
"@babel/preset-react"
]
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
]
},
plugins: [
new HtmlWebPackPlugin({
title: 'Playwright Test Report',
template: path.join(__dirname, 'src', 'index.html'),
inject: true,
}),
new BundleJsPlugin(),
]
};

View File

@ -17,7 +17,7 @@
import * as channels from '../protocol/channels';
import type { Size } from '../common/types';
export { Size, Point, Rect, Quad, URLMatch, TimeoutOptions, HeadersArray } from '../common/types';
export type { Size, Point, Rect, Quad, URLMatch, TimeoutOptions, HeadersArray } from '../common/types';
type LoggerSeverity = 'verbose' | 'info' | 'warning' | 'error';
export interface Logger {

View File

@ -17,7 +17,8 @@
// This file is generated by generate_channels.js, do not edit manually.
import { Validator, ValidationError, tOptional, tObject, tBoolean, tNumber, tString, tAny, tEnum, tArray, tBinary } from './validatorPrimitives';
export { Validator, ValidationError } from './validatorPrimitives';
export type { Validator } from './validatorPrimitives';
export { ValidationError } from './validatorPrimitives';
type Scheme = { [key: string]: Validator };

View File

@ -18,7 +18,7 @@ import type { Fixtures, TestError } from '../types/test';
import type { Location } from '../types/testReporter';
import type { FullConfig as FullConfigPublic } from './types';
export * from '../types/test';
export { Location } from '../types/testReporter';
export type { Location } from '../types/testReporter';
export type FixturesWithLocation = {
fixtures: Fixtures;

View File

@ -26,7 +26,7 @@ import { isInternalFileName } from 'playwright-core/lib/utils/stackTrace';
import { currentTestInfo } from './globals';
import { captureStackTrace as coreCaptureStackTrace, ParsedStackTrace } from 'playwright-core/lib/utils/stackTrace';
export { ParsedStackTrace };
export type { ParsedStackTrace };
const PLAYWRIGHT_CORE_PATH = path.dirname(require.resolve('playwright-core'));
const EXPECT_PATH = path.dirname(require.resolve('expect'));

View File

@ -26,6 +26,7 @@
"include": ["packages"],
"exclude": [
"packages/*/lib",
"packages/html-reporter",
"packages/recorder",
"packages/trace-viewer",
"packages/web",

View File

@ -192,8 +192,6 @@ steps.push({
// Build injected scripts.
const webPackFiles = [
'packages/playwright-core/src/server/injected/webpack.config.js',
'packages/html-reporter/webpack.config.js',
'packages/html-reporter/tests/webpack.config.js',
];
for (const file of webPackFiles) {
steps.push({
@ -253,34 +251,22 @@ onChanges.push({
script: 'utils/generate_types/index.js',
});
// Update web clients.
onChanges.push({
// Rebuild web projects on change.
for (const webPackage of ['html-reporter', 'recorder', 'trace-viewer']) {
onChanges.push({
committed: false,
inputs: [
'packages/trace-viewer/index.html',
'packages/trace-viewer/pubic/',
'packages/trace-viewer/src/',
'packages/trace-viewer/view.config.ts',
'packages/web/src/',
`packages/${webPackage}/index.html`,
`packages/${webPackage}/pubic/`,
`packages/${webPackage}/src/`,
`packages/${webPackage}/view.config.ts`,
`packages/web/src/`,
],
command: 'npx',
args: ['vite', 'build'],
cwd: path.join(__dirname, '..', '..', 'packages', 'trace-viewer'),
});
onChanges.push({
committed: false,
inputs: [
'packages/recorder/index.html',
'packages/recorder/pubic/',
'packages/recorder/src/',
'packages/recorder/view.config.ts',
'packages/web/src/',
],
command: 'npx',
args: ['vite', 'build'],
cwd: path.join(__dirname, '..', '..', 'packages', 'recorder'),
});
cwd: path.join(__dirname, '..', '..', 'packages', webPackage),
});
}
// The recorder and trace viewer have an app_icon.png that needs to be copied.
copyFiles.push({
@ -321,16 +307,13 @@ if (lintMode) {
args: ['tsc', ...(watchMode ? ['-w'] : []), '-p', quotePath(filePath('.'))],
shell: true,
});
for (const webPackage of ['html-reporter', 'recorder', 'trace-viewer']) {
steps.push({
command: 'npx',
args: ['tsc', ...(watchMode ? ['-w'] : []), '-p', quotePath(filePath('packages/recorder'))],
shell: true,
});
steps.push({
command: 'npx',
args: ['tsc', ...(watchMode ? ['-w'] : []), '-p', quotePath(filePath('packages/trace-viewer'))],
args: ['tsc', ...(watchMode ? ['-w'] : []), '-p', quotePath(filePath(`packages/${webPackage}`))],
shell: true,
});
}
}
watchMode ? runWatch() : runBuild();

View File

@ -146,7 +146,7 @@ async function innerCheckDeps(root, checkDepsFile, checkPackageJson) {
while (!depsFile[from]) {
if (from.lastIndexOf('/') === -1)
throw new Error(`Cannot find DEPS for ${fromDirectory}`);
return false;
from = from.substring(0, from.lastIndexOf('/'));
}
@ -180,7 +180,7 @@ async function innerCheckDeps(root, checkDepsFile, checkPackageJson) {
if (error.code !== 'MODULE_NOT_FOUND')
throw error;
}
return !!packageJSON.dependencies[importName];
return !!(packageJSON.dependencies || {})[importName];
}
}

View File

@ -150,7 +150,8 @@ const validator_ts = [
// This file is generated by ${path.basename(__filename)}, do not edit manually.
import { Validator, ValidationError, tOptional, tObject, tBoolean, tNumber, tString, tAny, tEnum, tArray, tBinary } from './validatorPrimitives';
export { Validator, ValidationError } from './validatorPrimitives';
export type { Validator } from './validatorPrimitives';
export { ValidationError } from './validatorPrimitives';
type Scheme = { [key: string]: Validator };