Skip to content

Commit 99b3687

Browse files
committed
ci: Add new metrics overhead app
To hopefully be able to debug replay issues a bit better, and have a bit more representative checking.
1 parent 67b0684 commit 99b3687

File tree

9 files changed

+626
-8
lines changed

9 files changed

+626
-8
lines changed

packages/overhead-metrics/configs/ci/collect.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Metrics } from '../../src/collector.js';
22
import { MetricsCollector } from '../../src/collector.js';
33
import type { NumberProvider } from '../../src/results/metrics-stats.js';
44
import { MetricsStats } from '../../src/results/metrics-stats.js';
5-
import { JankTestScenario } from '../../src/scenarios.js';
5+
import { BookingAppScenario } from '../../src/scenarios.js';
66
import { printStats } from '../../src/util/console.js';
77
import { latestResultFile } from './env.js';
88

@@ -26,9 +26,9 @@ const collector = new MetricsCollector({ headless: true, cpuThrottling: 2 });
2626
const result = await collector.execute({
2727
name: 'jank',
2828
scenarios: [
29-
new JankTestScenario('index.html'),
30-
new JankTestScenario('with-sentry.html'),
31-
new JankTestScenario('with-replay.html'),
29+
new BookingAppScenario('index.html', 100),
30+
new BookingAppScenario('with-sentry.html', 100),
31+
new BookingAppScenario('with-replay.html', 100),
3232
],
3333
runs: 10,
3434
tries: 10,

packages/overhead-metrics/configs/dev/collect.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import type { Metrics } from '../../src/collector.js';
22
import { MetricsCollector } from '../../src/collector.js';
33
import { MetricsStats } from '../../src/results/metrics-stats.js';
4-
import { JankTestScenario } from '../../src/scenarios.js';
4+
import { BookingAppScenario } from '../../src/scenarios.js';
55
import { printStats } from '../../src/util/console.js';
66
import { latestResultFile } from './env.js';
77

88
const collector = new MetricsCollector();
99
const result = await collector.execute({
1010
name: 'dummy',
1111
scenarios: [
12-
new JankTestScenario('index.html'),
13-
new JankTestScenario('with-sentry.html'),
14-
new JankTestScenario('with-replay.html'),
12+
new BookingAppScenario('index.html', 50),
13+
new BookingAppScenario('with-sentry.html', 50),
14+
new BookingAppScenario('with-replay.html', 50),
15+
new BookingAppScenario('index.html', 500),
16+
new BookingAppScenario('with-sentry.html', 500),
17+
new BookingAppScenario('with-replay.html', 500),
1518
],
1619
runs: 1,
1720
tries: 1,

packages/overhead-metrics/src/results/metrics-stats.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ export class MetricsStats {
5151
private static _filteredValues(numbers: number[]): number[] {
5252
numbers.sort((a, b) => a - b);
5353

54+
if (numbers.length < 1) {
55+
return [];
56+
}
57+
5458
const q1 = ss.quantileSorted(numbers, 0.25);
5559
const q3 = ss.quantileSorted(numbers, 0.75);
5660
const iqr = q3 - q1;

packages/overhead-metrics/src/scenarios.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,28 @@ export class JankTestScenario implements Scenario {
5353
await new Promise(resolve => setTimeout(resolve, 12000));
5454
}
5555
}
56+
57+
export class BookingAppScenario implements Scenario {
58+
public constructor(private _indexFile: string, private _count: number) {}
59+
60+
/**
61+
*
62+
*/
63+
public async run(_: playwright.Browser, page: playwright.Page): Promise<void> {
64+
let url = path.resolve(`./test-apps/booking-app/${this._indexFile}`);
65+
assert(fs.existsSync(url));
66+
url = `file:///${url.replace(/\\/g, '/')}?count=${this._count}`;
67+
console.log('Navigating to ', url);
68+
await page.goto(url, { waitUntil: 'load', timeout: 60000 });
69+
70+
// Click "Update"
71+
await page.click('#search button');
72+
73+
for (let i = 1; i < 10; i++) {
74+
await page.click(`.result:nth-child(${i}) [data-select]`);
75+
}
76+
77+
// Wait for flushing, which we set to 2000ms
78+
await new Promise(resolve => setTimeout(resolve, 2000));
79+
}
80+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<!DOCTYPE html>
2+
<html lang="en" dir="ltr">
3+
<head>
4+
<title>Demo Booking Engine</title>
5+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
7+
<meta name="robots" content="noindex" />
8+
9+
<link rel="stylesheet" href="./style.css" />
10+
</head>
11+
12+
<body>
13+
<div class="">
14+
<div class="header">
15+
<h1>This is a test app.</h1>
16+
17+
<div class="search-form">
18+
<form name="search" id="search" data-form-state="0" action="" method="POST">
19+
<div class="search-form-fields">
20+
<div class="field">
21+
<label for="check-in">Check-in</label>
22+
<input name="check-in" type="date" class="input" />
23+
</div>
24+
25+
<div class="field">
26+
<label for="check-out">Check-out</label>
27+
<input name="check-out" type="date" class="input" />
28+
</div>
29+
30+
<div class="field">
31+
<label for="adults">Number of persons</label>
32+
<select name="adults">
33+
<option value="1">1</option>
34+
<option value="2" selected="">2</option>
35+
<option value="3">3</option>
36+
<option value="4">4</option>
37+
<option value="5">5</option>
38+
<option value="6">6</option>
39+
<option value="7">7</option>
40+
<option value="8">8</option>
41+
<option value="9">9</option>
42+
<option value="10">10</option>
43+
<option value="11">11</option>
44+
<option value="12">12</option>
45+
<option value="13">13</option>
46+
<option value="14">14</option>
47+
<option value="15">15</option>
48+
<option value="16">16</option>
49+
<option value="17">17</option>
50+
<option value="18">18</option>
51+
<option value="19">19</option>
52+
<option value="20">20</option>
53+
<option value="21">21</option>
54+
<option value="22">22</option>
55+
<option value="23">23</option>
56+
<option value="24">24</option>
57+
<option value="25">25</option>
58+
<option value="26">26</option>
59+
<option value="27">27</option>
60+
<option value="28">28</option>
61+
<option value="29">29</option>
62+
<option value="30">30</option>
63+
</select>
64+
</div>
65+
66+
<div class="field">
67+
<button type="submit">Update</button>
68+
</div>
69+
</div>
70+
</form>
71+
</div>
72+
73+
<div>
74+
<form name="book" id="book" method="post">
75+
<div class="result-list"></div>
76+
</form>
77+
</div>
78+
</div>
79+
</div>
80+
81+
<script src="./main.js"></script>
82+
</body>
83+
</html>
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
(function () {
2+
const searchForm = document.querySelector('#search');
3+
4+
searchForm.addEventListener('submit', event => {
5+
event.preventDefault();
6+
7+
updateOffers();
8+
});
9+
10+
const obs = new MutationObserver(function (mutations) {
11+
console.log(mutations);
12+
});
13+
14+
obs.observe(document.documentElement, {
15+
attributes: true,
16+
attributeOldValue: true,
17+
characterData: true,
18+
characterDataOldValue: true,
19+
childList: true,
20+
subtree: true,
21+
});
22+
})();
23+
24+
function updateOffers() {
25+
const list = document.querySelector('.result-list');
26+
27+
// Clear out existing children
28+
for (let el of list.children) {
29+
list.removeChild(el);
30+
}
31+
32+
// Add new children
33+
// Allow to define children count via URL ?count=100
34+
const url = new URL(window.location.href);
35+
const count = parseInt(url.searchParams.get('count') || 50);
36+
for (let i = 0; i < count; i++) {
37+
const el = document.createElement('div');
38+
el.classList.add('result');
39+
el.innerHTML = generateResult();
40+
41+
const id = crypto.randomUUID();
42+
el.setAttribute('id', id);
43+
44+
addListeners(id, el);
45+
46+
list.appendChild(el);
47+
}
48+
}
49+
50+
function addListeners(id, el) {
51+
el.querySelector('[data-long-text-open]').addEventListener('click', event => {
52+
const parent = event.target.closest('.long-text');
53+
parent.setAttribute('data-show-long', '');
54+
});
55+
el.querySelector('[data-long-text-close]').addEventListener('click', event => {
56+
const parent = event.target.closest('.long-text');
57+
parent.removeAttribute('data-show-long');
58+
});
59+
60+
// These are purposefully inefficient
61+
el.querySelector('[data-select]').addEventListener('click', () => {
62+
document.querySelectorAll('.result').forEach(result => {
63+
if (result.getAttribute('id') === id) {
64+
result.setAttribute('data-show-options', 'yes');
65+
} else {
66+
result.setAttribute('data-show-options', 'no');
67+
}
68+
});
69+
70+
// Do some more, extra expensive work
71+
document.querySelectorAll('.select__price').forEach(el => {
72+
el.setAttribute('js-is-checked', new Date().toISOString());
73+
el.setAttribute('js-is-checked-2', new Date().toISOString());
74+
el.setAttribute('js-is-checked-3', 'yes');
75+
el.setAttribute('js-is-checked-4', 'yes');
76+
el.setAttribute('js-is-checked-5', 'yes');
77+
el.setAttribute('js-is-checked-6', 'yes');
78+
});
79+
document.querySelectorAll('.tag').forEach(el => el.setAttribute('js-is-checked', 'yes'));
80+
document.querySelectorAll('h3').forEach(el => el.setAttribute('js-is-checked', 'yes'));
81+
});
82+
}
83+
84+
const baseTitles = ['Cottage house', 'Cabin', 'Villa', 'House', 'Appartment', 'Cosy appartment'];
85+
const baseBeds = ['2', '2+2', '4+2', '6+2', '6+4'];
86+
const baseDescription =
87+
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
88+
89+
function generateResult() {
90+
const title = `${getRandomItem(baseTitles)} ${Math.ceil(Math.random() * 20)}`;
91+
const beds = getRandomItem(baseBeds);
92+
const description = baseDescription
93+
.split(' ')
94+
.slice(Math.ceil(Math.random() * 10))
95+
.join(' ');
96+
const price = 200 + Math.random() * 800;
97+
98+
// Make short version of description
99+
const descriptionShort = description.slice(0, 200);
100+
const priceStr = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(price);
101+
102+
const placeholders = {
103+
title,
104+
beds,
105+
description,
106+
descriptionShort,
107+
priceStr,
108+
};
109+
110+
return replacePlaceholders(template, placeholders);
111+
}
112+
113+
function getRandomItem(list) {
114+
return list[Math.floor(Math.random() * list.length)];
115+
}
116+
117+
function replacePlaceholders(str, placeholders) {
118+
let replacedStr = str;
119+
Object.keys(placeholders).forEach(placeholder => {
120+
replacedStr = replacedStr.replaceAll(`{{${placeholder}}}`, placeholders[placeholder]);
121+
});
122+
123+
return replacedStr;
124+
}
125+
126+
const template = `<figure class="result-image">
127+
<img src="https://api.lorem.space/image/house?w=350&h=250" alt="{{title}}" data-image />
128+
</figure>
129+
130+
<div class="result-content">
131+
<div>
132+
<h3>{{title}}</h3>
133+
134+
<div class="tags">
135+
<span class="tag">{{beds}}</span>
136+
</div>
137+
</div>
138+
139+
<div class="long-text">
140+
<div class="long-text__short">
141+
{{descriptionShort}}<button type="button" data-long-text-open>... Read more</button>
142+
</div>
143+
144+
<div class="long-text__long">
145+
{{description}}
146+
<button type="button" data-long-text-close>Read less</button>
147+
</div>
148+
</div>
149+
150+
<div class="select">
151+
<button type="button" data-select>
152+
<div aria-hidden="true" class="icon">+</div>
153+
<div>Select</div>
154+
155+
<div class="select__price">
156+
<div class="price__amount">{{priceStr}}</div>
157+
<div class="price__quantity-label">/night</div>
158+
</div>
159+
</button>
160+
</div>
161+
162+
<div class="options">
163+
<div class="field">
164+
<select>
165+
<option value="0">0 rooms</option>
166+
<option value="1">1 room</option>
167+
</select>
168+
</div>
169+
170+
<div class="field">
171+
<select>
172+
<option value="1">1 guest</option>
173+
<option value="2">2 guests</option>
174+
</select>
175+
</div>
176+
</div>
177+
</div>`;

0 commit comments

Comments
 (0)