├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── LICENSE.md
├── README.md
├── demo-module
├── index.css
├── index.js
└── module.js
├── package-lock.json
├── package.json
├── src
├── browser.ts
├── index.performance.ts
├── index.ts
└── types.d.ts
├── tsconfig.json
├── tsconfig.lib.json
└── web-test-runner.config.mjs
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 |
9 | steps:
10 | - uses: actions/checkout@v2
11 |
12 | - name: install playwright
13 | run: npx playwright install-deps
14 |
15 | - name: install
16 | run: npm ci
17 |
18 | - name: build
19 | run: npm run ci
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 0.1.6
4 | - fix: dependency updates
5 |
6 | ## 0.1.5
7 | - fix: update deps to rollup 4
8 |
9 | ## 0.1.4
10 | - feat: flag to disable optimizations for pre-built assets reducing test time up to 30%
11 | - feat: customizations of bundle options `optimize` and `external` per unit test
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Cory Rylan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Crylan Software
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Web Test Runner Performance
2 |
3 | [](https://badge.fury.io/js/web-test-runner-performance) 
4 |
5 | The Web Test Runner Performance provides plugins for [web-test-runner](https://modern-web.dev/docs/test-runner/overview/) to measure UI performance as well as a custom reporter for logging results. The package provides three plugins for your `web-test-runner` configuration.
6 |
7 | - `bundlePerformancePlugin`: measure bundle/package size output
8 | - `renderPerformancePlugin`: measure component render performance
9 | - `performanceReporter`: for writting performance results to disk
10 |
11 | ## Setup
12 |
13 | Below you can find a minimal setup. To use `web-test-runner-performance` create a standalone test runner separate from your standard test runner config. Performance tests should be run independent of other tests and only run one test and browser at a time for the most accurate results.
14 |
15 | ```javascript
16 | // web-test-runner.performance.mjs
17 | import { defaultReporter } from '@web/test-runner';
18 | import { esbuildPlugin } from '@web/dev-server-esbuild';
19 | import { playwrightLauncher } from '@web/test-runner-playwright';
20 |
21 | export default ({
22 | concurrency: 1,
23 | concurrentBrowsers: 1,
24 | nodeResolve: true,
25 | testsFinishTimeout: 20000,
26 | files: ['./src/*.performance.ts'],
27 | browsers: [playwrightLauncher({ product: 'chromium', launchOptions: { headless: false } })],
28 | reporters: [
29 | defaultReporter({ reportTestResults: true, reportTestProgress: true })
30 | ],
31 | plugins: [
32 | esbuildPlugin({ ts: true, json: true, target: 'auto', sourceMap: true }),
33 | ],
34 | });
35 | ```
36 | ## Bundle Performance
37 |
38 | The `testBundleSize` takes a JavaScript module and will bunle any imports including CSS providing
39 | a final output size in Kb. The bundle is generated by Rollup and will treeshake and minify the code.
40 | This is helpful for testing library code ensuring it properly treeeshakes.
41 | You can also pass an alias config to test local file paths.
42 |
43 | ```javascript
44 | // web-test-runner.performance.mjs
45 | ...
46 | import { bundlePerformancePlugin } from 'web-test-runner-performance';
47 |
48 | export default ({
49 | ...
50 | plugins: [
51 | bundlePerformancePlugin(),
52 | ]
53 | });
54 | ```
55 |
56 | ```javascript
57 | // component.performance.js
58 | import { expect } from '@esm-bundle/chai';
59 | import { testBundleSize } from 'web-test-runner-performance/browser.js';
60 |
61 | describe('performance', () => {
62 | it('should meet maximum css bundle size limits (0.2kb brotli)', async () => {
63 | expect((await testBundleSize('./demo-module/index.css')).kb).to.below(0.2);
64 | });
65 |
66 | it('should meet maximum js bundle size limits (0.78kb brotli)', async () => {
67 | expect((await testBundleSize('./demo-module/index.js')).kb).to.below(0.8);
68 | });
69 | });
70 | ```
71 |
72 | You can also pass in an alias configuration to alias imports for local packages.
73 |
74 | ```javascript
75 | bundlePerformancePlugin({
76 | aliases: [{ find: /^demo-module$/, replacement: `${process.cwd()}/demo-module` }]
77 | })
78 | ```
79 |
80 | By default bundle test will minify CSS and JavaScript. If your build/assets are already
81 | minified you can disable this option to improve test speed.
82 |
83 | ```javascript
84 | bundlePerformancePlugin({
85 | optimize: false
86 | }),
87 | ```
88 |
89 | Both `optimize` and `external` can be customized at a test level to override the global settings.
90 |
91 | ```javascript
92 | it('should meet maximum js bundle size limits (0.78kb brotli)', async () => {
93 | expect((await testBundleSize('./demo-module/index.js', { optimize: false, external: [] })).kb).to.below(0.8);
94 | });
95 | ```
96 |
97 | ## Render Performance
98 |
99 | The `renderPerformancePlugin` will measure the render time of a given custom element in milliseconds.
100 |
101 | ```javascript
102 | // web-test-runner.performance.mjs
103 | ...
104 | import { renderPerformancePlugin } from 'web-test-runner-performance';
105 |
106 | export default ({
107 | ...
108 | plugins: [
109 | esbuildPlugin({ ts: true, json: true, target: 'auto', sourceMap: true }),
110 | renderPerformancePlugin(),
111 | ],
112 | });
113 | ```
114 |
115 | Once the plugin is added a test can be written to check how quick an element can be rendered.
116 |
117 | ```javascript
118 | // component.performance.js
119 | import { expect } from '@esm-bundle/chai';
120 | import { testBundleSize, testRenderTime, html } from 'web-test-runner-performance/browser.js';
121 |
122 | describe('performance', () => {
123 | it(`should meet maximum render time 1000
below 50ms`, async () => {
124 | const result = await testRenderTime(html`hello world`, { iterations: 1000, average: 10 });
125 | expect(result.average.length).to.eql(10);
126 | expect(result.duration).to.below(50);
127 | });
128 | });
129 | ```
130 |
131 | The `testRenderTime` takes a template of the component to render and will return how long the
132 | element took to render its first paint in miliseconds. The config accepts an `iterations` property to render
133 | the template n+ iterations. By default the test runner will render three times and
134 | average the results. This can be customized using the `average` property.
135 |
136 | ## Reporter
137 |
138 | The `performanceReporter` provides a custom reporter which will write all test
139 | results to a json file.
140 |
141 | ```javascript
142 | // web-test-runner.performance.mjs
143 | import { playwrightLauncher } from '@web/test-runner-playwright';
144 | import { esbuildPlugin } from '@web/dev-server-esbuild';
145 | import { defaultReporter } from '@web/test-runner';
146 | import { bundlePerformancePlugin, renderPerformancePlugin, performanceReporter } from 'web-test-runner-performance';
147 |
148 | export default ({
149 | concurrency: 1,
150 | concurrentBrowsers: 1,
151 | nodeResolve: true,
152 | testsFinishTimeout: 20000,
153 | files: ['./src/*.performance.ts'],
154 | browsers: [playwrightLauncher({ product: 'chromium', launchOptions: { headless: false } })],
155 | reporters: [
156 | defaultReporter({ reportTestResults: true, reportTestProgress: true }),
157 | performanceReporter({ writePath: `${process.cwd()}/dist/performance` })
158 | ],
159 | plugins: [
160 | esbuildPlugin({ ts: true, json: true, target: 'auto', sourceMap: true }),
161 | renderPerformancePlugin(),
162 | bundlePerformancePlugin(),
163 | ]
164 | });
165 | ```
166 |
167 | ```json
168 | // dist/performance/report.json
169 | {
170 | "renderTimes": [
171 | {
172 | "testFile": "/src/index.performance.ts",
173 | "duration": 27.559999999999995
174 | }
175 | ],
176 | "bundleSizes": [
177 | {
178 | "testFile": "/src/index.performance.ts",
179 | "compression": "brotli",
180 | "kb": 0.193
181 | },
182 | {
183 | "testFile": "/src/index.performance.ts",
184 | "compression": "brotli",
185 | "kb": 0.78
186 | }
187 | ]
188 | }
189 | ```
190 |
191 | Learn more about getting started here https://coryrylan.com/blog/testing-web-performance-with-web-test-runner
192 |
--------------------------------------------------------------------------------
/demo-module/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: blue;
3 | }
4 |
--------------------------------------------------------------------------------
/demo-module/index.js:
--------------------------------------------------------------------------------
1 | import './module.js';
2 |
3 | console.log('module 1');
--------------------------------------------------------------------------------
/demo-module/module.js:
--------------------------------------------------------------------------------
1 | const longString = `
2 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
3 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
4 | Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
5 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
6 | Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam,
7 | eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam
8 | voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem
9 | sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non
10 | numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis
11 | nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum
12 | iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo
13 | voluptas nulla pariatur? Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
14 | labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
15 | commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
16 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut
17 | perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa
18 | quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia
19 | voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.
20 | Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi
21 | tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem
22 | ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea
23 | voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? Lorem ipsum
24 | dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
25 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit
26 | in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
27 | officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque
28 | laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.
29 | Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione
30 | voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia
31 | non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis
32 | nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure
33 | reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
34 | pariatur? Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
35 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
36 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
37 | culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium
38 | doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt
39 | explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui
40 | ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed
41 | quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis
42 | nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure
43 | reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
44 | pariatur? Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
45 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
46 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
47 | culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium
48 | doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt
49 | explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui
50 | ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit,
51 | sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam,
52 | quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure
53 | reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
54 | pariatur? Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
55 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
56 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
57 | culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium
58 | doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt
59 | explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui
60 | ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed
61 | quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
62 | exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui
63 | in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? Lorem ipsum
64 | dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam
65 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
66 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
67 | Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
68 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
69 | Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam,
70 | eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam
71 | voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem
72 | sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non
73 | numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis
74 | nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum
75 | iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo
76 | voluptas nulla pariatur? Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
77 | labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
78 | commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
79 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut
80 | perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa
81 | quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia
82 | voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.
83 | Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi
84 | tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem
85 | ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea
86 | voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? Lorem ipsum
87 | dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
88 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit
89 | in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
90 | officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque
91 | laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.
92 | Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione
93 | voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia
94 | non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis
95 | nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure
96 | reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
97 | pariatur? Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
98 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
99 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
100 | culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium
101 | doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt
102 | explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui
103 | ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed
104 | quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis
105 | nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure
106 | reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
107 | pariatur? Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
108 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
109 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
110 | culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium
111 | doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt
112 | explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui
113 | ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit,
114 | sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam,
115 | quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure
116 | reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
117 | pariatur? Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
118 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
119 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
120 | culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium
121 | doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt
122 | explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui
123 | ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed
124 | quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
125 | exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui
126 | in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? Lorem ipsum
127 | dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam`;
128 | console.log(longString);
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web-test-runner-performance",
3 | "version": "0.1.6",
4 | "description": "",
5 | "main": "./index.js",
6 | "module": "./index.js",
7 | "typings": "./index.d.ts",
8 | "type": "module",
9 | "files": [
10 | "*"
11 | ],
12 | "scripts": {
13 | "ci": "npm run clean && npm run build && npm run test",
14 | "clean": "del ./dist",
15 | "test": "web-test-runner",
16 | "build": "tsc --project ./tsconfig.lib.json && cpy ./package.json dist/lib/ && cpy ./README.md dist/lib/ && cpy ./LICENSE.md dist/lib/",
17 | "build:watch": "tsc --watch --project ./tsconfig.lib.json"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/coryrylan/web-test-runner-performance.git"
22 | },
23 | "author": "Cory Rylan",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/coryrylan/web-test-runner-performance/issues"
27 | },
28 | "homepage": "https://github.com/coryrylan/web-test-runner-performance#readme",
29 | "devDependencies": {
30 | "@esm-bundle/chai": "^4.3.4-fix.0",
31 | "@types/fs-extra": "^11.0.4",
32 | "typescript": "^5.4.5"
33 | },
34 | "overrides": {
35 | "rollup-plugin-styles": {
36 | "cssnano": "6.1.2"
37 | }
38 | },
39 | "dependencies": {
40 | "@rollup/plugin-alias": "^5.1.0",
41 | "@rollup/plugin-virtual": "^3.0.2",
42 | "@web/dev-server-esbuild": "^1.0.2",
43 | "@web/dev-server-rollup": "^0.6.1",
44 | "@rollup/plugin-terser": "^0.4.4",
45 | "@web/test-runner": "^0.18.1",
46 | "@web/test-runner-commands": "^0.9.0",
47 | "@web/test-runner-playwright": "^0.11.0",
48 | "brotli-size": "^4.0.0",
49 | "cpy-cli": "^5.0.0",
50 | "del-cli": "^5.1.0",
51 | "fs-extra": "^11.2.0",
52 | "lit": "^3.1.2",
53 | "rollup-plugin-shell": "^1.0.9",
54 | "rollup-plugin-styles": "^4.0.0",
55 | "tslib": "^2.6.2"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/browser.ts:
--------------------------------------------------------------------------------
1 | import { executeServerCommand } from '@web/test-runner-commands';
2 | import { TemplateResult, render, html } from 'lit';
3 |
4 | export { html } from 'lit';
5 |
6 | export async function testBundleSize(bundle: string, config: { optimize?: boolean, external?: (string | RegExp)[] } = { }) {
7 | return await executeServerCommand('performance:bundle', { bundle, config });
8 | }
9 |
10 | export async function testRenderTime(template: TemplateResult<1>, config: { iterations?: number, average?: number } = { }) {
11 | const conf = { iterations: 1, average: 3, ...config };
12 | let averages = [];
13 |
14 | for await (let result of averageRender(template, conf)) {
15 | averages.push(result);
16 | }
17 |
18 | const duration = averages.reduce((p, n) => p + n.entry.duration, 0) / conf.average;
19 | const result = { duration, iterations: config.iterations, average: config.average };
20 | await executeServerCommand('performance:render', result);
21 | return result;
22 | }
23 |
24 | async function* averageRender(template: TemplateResult<1>, config: { iterations: number, average: number }) {
25 | let i = 0;
26 | while (i < config.average) {
27 | const element = document.createElement('div');
28 | element.setAttribute('performance', '');
29 | document.body.appendChild(element);
30 | render(html`${Array.from(Array(config.iterations).keys()).map(() => template)}`, element);
31 | const detail: any = await measureElementRender(element);
32 | element.remove();
33 | i++;
34 | yield detail;
35 | }
36 | }
37 |
38 | export function measureElementRender(element: HTMLElement, markId = `_${Math.random().toString(36).substr(2, 9)}`) {
39 | return new Promise(resolve => {
40 | performance.mark(`${markId}-start`);
41 |
42 | const observer = new ResizeObserver(async (mutation) => {
43 | const { width, height } = mutation[0].contentRect;
44 | if (width > 0 && height > 0) {
45 | observer.disconnect();
46 | await new Promise(r => setTimeout(() => r('')));
47 | performance.mark(`${markId}-end`);
48 | performance.measure(markId, `${markId}-start`, `${markId}-end`);
49 |
50 | const entry = performance.getEntriesByName(markId).pop();
51 | const duration = Number.parseFloat(entry.duration.toPrecision(5));
52 | const detail = { entry: { ...entry, duration } };
53 |
54 | performance.clearMarks(`${markId}-start`);
55 | performance.clearMarks(`${markId}-end`);
56 | performance.clearMeasures(markId);
57 | resolve(detail);
58 | }
59 | });
60 |
61 | observer.observe(element);
62 | });
63 | }
64 |
--------------------------------------------------------------------------------
/src/index.performance.ts:
--------------------------------------------------------------------------------
1 | import { expect } from '@esm-bundle/chai';
2 | import { testBundleSize, testRenderTime, html } from '../dist/lib/browser.js';
3 |
4 | describe('performance', () => {
5 | it('should meet maximum css bundle size limits (0.03kb brotli)', async () => {
6 | expect((await testBundleSize('demo-module/index.css')).kb).to.below(0.03);
7 | });
8 |
9 | it('should meet maximum js bundle size limits (0.8kb brotli)', async () => {
10 | expect((await testBundleSize('demo-module/index.js')).kb).to.below(0.8);
11 | });
12 |
13 | it('should meet maximum render time 1000 below 50ms', async () => {
14 | const result = await testRenderTime(html`
hello world
`, { iterations: 1000, average: 10 });
15 | expect(result.duration).to.below(50);
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { nodeResolve } from '@rollup/plugin-node-resolve';
2 | import terser from '@rollup/plugin-terser';
3 | import alias from '@rollup/plugin-alias';
4 | import virtual from '@rollup/plugin-virtual';
5 | import styles from 'rollup-plugin-styles';
6 | import * as rollup from 'rollup';
7 | import * as brotliSize from 'brotli-size';
8 | import fs from 'fs-extra';
9 |
10 | const { ensureFileSync } = fs;
11 |
12 | const store = {
13 | renderTimes: [],
14 | bundleSizes: []
15 | }
16 |
17 | export interface BundleConfig {
18 | writePath?: string;
19 | external?: (string | RegExp)[];
20 | aliases?: { find: string | RegExp; replacement: string; }[],
21 | optimize?: boolean
22 | }
23 |
24 | export function bundlePerformancePlugin(config) {
25 | return {
26 | name: 'bundle-performance-plugin',
27 | async executeCommand({ command, payload, session }) {
28 | return command === 'performance:bundle' ? await measureBundleSize(session, payload.bundle, { ...config, ...payload.config }) : null;
29 | }
30 | }
31 | }
32 |
33 | export function renderPerformancePlugin() {
34 | return {
35 | name: 'render-performance-plugin',
36 | async executeCommand({ command, payload, session }) {
37 | return command === 'performance:render' ? await measureRenderTime(session, payload) : null;
38 | }
39 | }
40 | }
41 |
42 | export function performanceReporter(config: { writePath: string }) {
43 | return {
44 | stop() {
45 | const path = `${config.writePath}/report.json`;
46 | ensureFileSync(path);
47 | fs.writeJsonSync(path, store, { spaces: 2 });
48 | }
49 | };
50 | }
51 |
52 | async function measureBundleSize(session: any, entrypoint: string, bundleConfig: BundleConfig = { }) {
53 | const config: BundleConfig = { writePath: null, optimize: true, external: [], aliases: [], ...bundleConfig };
54 | const rollupConfig = {
55 | inputOptions: {
56 | input: 'entry',
57 | external: config.external,
58 | plugins: [
59 | virtual({ entry: `${entrypoint.includes('import') ? entrypoint : `import '${entrypoint}';`};console.log('entrypoint')` }),
60 | styles({ minimize: config.optimize, mode: 'extract' }),
61 | nodeResolve(),
62 | alias({ entries: config.aliases }),
63 | config.optimize ? terser({ ecma: 2020, output: { comments: false }, compress: { unsafe: true, passes: 2 } }) : [],
64 | ],
65 | },
66 | outputOptions: {
67 | dir: `${config.writePath}/${session.id}.${Math.random().toString(36).substr(2, 9)}`,
68 | sourcemap: !!config.writePath
69 | },
70 | };
71 |
72 | const bundle = await rollup.rollup(rollupConfig.inputOptions);
73 | const { output } = await bundle.generate(rollupConfig.outputOptions);
74 | const code = output[0].code; // js
75 | const source = (output[1] as any)?.source; // css extraction if available
76 | const kb = brotliSize.sync(source ?? code) / 1000;
77 |
78 | if (!!config.writePath) {
79 | await bundle.write(rollupConfig.outputOptions);
80 | }
81 |
82 | await bundle.close();
83 |
84 | store.bundleSizes.push({
85 | kb,
86 | testFile: session.testFile.replace(session.browser.config.rootDir, ''),
87 | compression: 'brotli',
88 | entrypoint
89 | });
90 |
91 | return { kb };
92 | }
93 |
94 | function measureRenderTime(session, payload) {
95 | const testFile = session.testFile.replace(session.browser.config.rootDir, '')
96 | store.renderTimes.push({
97 | testFile,
98 | duration: payload.duration,
99 | average: payload.averages,
100 | iterations: payload.iterations,
101 | });
102 | return new Promise(r => r('reported'));
103 | }
104 |
--------------------------------------------------------------------------------
/src/types.d.ts:
--------------------------------------------------------------------------------
1 | declare module '@rollup/plugin-virtual';
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2019",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "lib": ["es2020", "dom"],
7 | "emitDecoratorMetadata": false,
8 | "experimentalDecorators": true,
9 | "allowSyntheticDefaultImports": true,
10 | "resolveJsonModule": true,
11 | "declaration": true,
12 | "alwaysStrict": true,
13 | "noImplicitAny": false,
14 | "noImplicitReturns": true,
15 | "noImplicitThis": true,
16 | "noUnusedLocals": false,
17 | "noUnusedParameters": false,
18 | "strictFunctionTypes": true,
19 | "noFallthroughCasesInSwitch": true,
20 | "strictNullChecks": false,
21 | "sourceMap": false,
22 | "inlineSourceMap": false,
23 | "importHelpers": true,
24 | "rootDir": "./src",
25 | "baseUrl": "./",
26 | "skipLibCheck": true,
27 | "paths": {
28 | "web-test-runner-performance": ["./dist/lib"],
29 | "web-test-runner-performance/*": ["./dist/lib/*"]
30 | }
31 | },
32 | "include": ["src/**/*.ts", "src/**/*.d.ts"],
33 | "exclude": ["node_modules"]
34 | }
35 |
--------------------------------------------------------------------------------
/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "noEmit": false,
5 | "outDir": "./dist/lib",
6 | },
7 | "include": ["src/**/*.ts", "src/**/*.d.ts"],
8 | "exclude": ["dist", "node_modules", "src/**/*.spec.ts", "src/**/*.performance.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/web-test-runner.config.mjs:
--------------------------------------------------------------------------------
1 | import { playwrightLauncher } from '@web/test-runner-playwright';
2 | import { esbuildPlugin } from '@web/dev-server-esbuild';
3 | import { defaultReporter } from '@web/test-runner';
4 | import { bundlePerformancePlugin, renderPerformancePlugin, performanceReporter } from './dist/lib/index.js';
5 |
6 | export default /** @type {import("@web/test-runner").TestRunnerConfig} */ ({
7 | concurrency: 1,
8 | concurrentBrowsers: 1,
9 | nodeResolve: true,
10 | testsFinishTimeout: 60000,
11 | testFramework: {
12 | config: {
13 | ui: 'bdd',
14 | timeout: '60000',
15 | },
16 | },
17 | files: ['./src/*.performance.ts'],
18 | browsers: [playwrightLauncher({ product: 'chromium', launchOptions: { headless: !!process.env.GITHUB_ACTION } })],
19 | plugins: [
20 | esbuildPlugin({ ts: true, json: true, target: 'auto', sourceMap: true }),
21 | renderPerformancePlugin(),
22 | bundlePerformancePlugin({
23 | // optimize: false,
24 | // writePath: `./dist/performance`,
25 | aliases: [{ find: /^demo-module$/, replacement: `./demo-module` }]
26 | }),
27 | ],
28 | reporters: [
29 | defaultReporter({ reportTestResults: true, reportTestProgress: true }),
30 | performanceReporter({ writePath: `./dist/performance` })
31 | ]
32 | });
33 |
--------------------------------------------------------------------------------