Skip to content

Commit 745f0d4

Browse files
authored
Merge pull request #4056 from grafana/mergeBrowserInK6
Merge browser in k6
2 parents 50251d1 + d884406 commit 745f0d4

File tree

231 files changed

+17567
-848
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

231 files changed

+17567
-848
lines changed

.github/workflows/browser_e2e.yml

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: E2E
2+
on:
3+
# Enable manually triggering this workflow via the API or web UI
4+
workflow_dispatch:
5+
push:
6+
branches:
7+
- main
8+
pull_request:
9+
schedule:
10+
# At 06:00 AM UTC from Monday through Friday
11+
- cron: '0 6 * * 1-5'
12+
13+
defaults:
14+
run:
15+
shell: bash
16+
17+
jobs:
18+
test:
19+
strategy:
20+
matrix:
21+
go: [stable, tip]
22+
platform: [ubuntu-latest, windows-latest, macos-latest]
23+
runs-on: ${{ matrix.platform }}
24+
steps:
25+
- name: Checkout code
26+
if: matrix.go != 'tip' || matrix.platform != 'windows-latest'
27+
uses: actions/checkout@v4
28+
- name: Install Go
29+
if: matrix.go != 'tip' || matrix.platform != 'windows-latest'
30+
uses: actions/setup-go@v5
31+
with:
32+
go-version: 1.x
33+
- name: Install Go tip
34+
if: matrix.go == 'tip' && matrix.platform != 'windows-latest'
35+
env:
36+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
37+
run: |
38+
gh release download ${{ matrix.platform }} --repo grafana/gotip --pattern 'go.zip'
39+
unzip go.zip -d $HOME/sdk
40+
echo "GOROOT=$HOME/sdk/gotip" >> "$GITHUB_ENV"
41+
echo "GOPATH=$HOME/go" >> "$GITHUB_ENV"
42+
echo "$HOME/go/bin" >> "$GITHUB_PATH"
43+
echo "$HOME/sdk/gotip/bin" >> "$GITHUB_PATH"
44+
- name: Build k6
45+
if: matrix.go != 'tip' || matrix.platform != 'windows-latest'
46+
run: |
47+
which go
48+
go version
49+
50+
go build .
51+
./k6 version
52+
- name: Run E2E tests
53+
if: matrix.go != 'tip' || matrix.platform != 'windows-latest'
54+
run: |
55+
set -x
56+
if [ "$RUNNER_OS" == "Linux" ]; then
57+
export K6_BROWSER_EXECUTABLE_PATH=/usr/bin/google-chrome
58+
fi
59+
export K6_BROWSER_HEADLESS=true
60+
for f in examples/browser/*.js; do
61+
if [ "$f" == "examples/browser/hosts.js" ] && [ "$RUNNER_OS" == "Windows" ]; then
62+
echo "skipping $f on Windows"
63+
continue
64+
fi
65+
./k6 run -q "$f"
66+
done
67+
- name: Check screenshot
68+
if: matrix.go != 'tip' || matrix.platform != 'windows-latest'
69+
# TODO: Do something more sophisticated?
70+
run: test -s screenshot.png

.golangci.yml

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ issues:
2626
- funlen
2727
- lll
2828
- forcetypeassert
29+
- path: js\/modules\/k6\/browser\/.*\.go
30+
linters:
31+
- revive
32+
- contextcheck
2933
- path: js\/modules\/k6\/html\/.*\.go
3034
text: "exported: exported "
3135
linters:

examples/browser/colorscheme.js

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { browser } from 'k6/browser';
2+
import { check } from 'https://jslib.k6.io/k6-utils/1.5.0/index.js';
3+
4+
export const options = {
5+
scenarios: {
6+
ui: {
7+
executor: 'shared-iterations',
8+
options: {
9+
browser: {
10+
type: 'chromium',
11+
},
12+
},
13+
},
14+
},
15+
thresholds: {
16+
checks: ["rate==1.0"]
17+
}
18+
}
19+
20+
export default async function() {
21+
const context = await browser.newContext({
22+
// valid values are "light", "dark" or "no-preference"
23+
colorScheme: 'dark',
24+
});
25+
const page = await context.newPage();
26+
27+
try {
28+
await page.goto(
29+
'https://test.k6.io',
30+
{ waitUntil: 'load' },
31+
)
32+
await check(page, {
33+
'isDarkColorScheme':
34+
p => p.evaluate(() => window.matchMedia('(prefers-color-scheme: dark)').matches)
35+
});
36+
} finally {
37+
await page.close();
38+
}
39+
}

examples/browser/cookies.js

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { browser } from 'k6/browser';
2+
import { check } from 'https://jslib.k6.io/k6-utils/1.5.0/index.js';
3+
4+
export const options = {
5+
scenarios: {
6+
ui: {
7+
executor: 'shared-iterations',
8+
options: {
9+
browser: {
10+
type: 'chromium',
11+
},
12+
},
13+
},
14+
},
15+
thresholds: {
16+
checks: ["rate==1.0"]
17+
}
18+
};
19+
20+
export default async function () {
21+
const page = await browser.newPage();
22+
const context = page.context();
23+
24+
try {
25+
// get cookies from the browser context
26+
await check(await context.cookies(), {
27+
'initial number of cookies should be zero': c => c.length === 0,
28+
});
29+
30+
// add some cookies to the browser context
31+
const unixTimeSinceEpoch = Math.round(new Date() / 1000);
32+
const day = 60*60*24;
33+
const dayAfter = unixTimeSinceEpoch+day;
34+
const dayBefore = unixTimeSinceEpoch-day;
35+
await context.addCookies([
36+
// this cookie expires at the end of the session
37+
{
38+
name: 'testcookie',
39+
value: '1',
40+
sameSite: 'Strict',
41+
domain: '127.0.0.1',
42+
path: '/',
43+
httpOnly: true,
44+
secure: true,
45+
},
46+
// this cookie expires in a day
47+
{
48+
name: 'testcookie2',
49+
value: '2',
50+
sameSite: 'Lax',
51+
domain: '127.0.0.1',
52+
path: '/',
53+
expires: dayAfter,
54+
},
55+
// this cookie expires in the past, so it will be removed.
56+
{
57+
name: 'testcookie3',
58+
value: '3',
59+
sameSite: 'Lax',
60+
domain: '127.0.0.1',
61+
path: '/',
62+
expires: dayBefore
63+
}
64+
]);
65+
let cookies = await context.cookies();
66+
await check(cookies.length, {
67+
'number of cookies should be 2': n => n === 2,
68+
});
69+
await check(cookies[0], {
70+
'cookie 1 name should be testcookie': c => c.name === 'testcookie',
71+
'cookie 1 value should be 1': c => c.value === '1',
72+
'cookie 1 should be session cookie': c => c.expires === -1,
73+
'cookie 1 should have domain': c => c.domain === '127.0.0.1',
74+
'cookie 1 should have path': c => c.path === '/',
75+
'cookie 1 should have sameSite': c => c.sameSite == 'Strict',
76+
'cookie 1 should be httpOnly': c => c.httpOnly === true,
77+
'cookie 1 should be secure': c => c.secure === true,
78+
});
79+
await check(cookies[1], {
80+
'cookie 2 name should be testcookie2': c => c.name === 'testcookie2',
81+
'cookie 2 value should be 2': c => c.value === '2',
82+
});
83+
84+
// let's add more cookies to filter by urls.
85+
await context.addCookies([
86+
{
87+
name: "foo",
88+
value: "42",
89+
sameSite: "Strict",
90+
url: "http://foo.com",
91+
},
92+
{
93+
name: "bar",
94+
value: "43",
95+
sameSite: "Lax",
96+
url: "https://bar.com",
97+
},
98+
{
99+
name: "baz",
100+
value: "44",
101+
sameSite: "Lax",
102+
url: "https://baz.com",
103+
},
104+
]);
105+
cookies = await context.cookies("http://foo.com", "https://baz.com");
106+
await check(cookies.length, {
107+
'number of filtered cookies should be 2': n => n === 2,
108+
});
109+
await check(cookies[0], {
110+
'the first filtered cookie name should be foo': c => c.name === 'foo',
111+
'the first filtered cookie value should be 42': c => c.value === '42',
112+
});
113+
await check(cookies[1], {
114+
'the second filtered cookie name should be baz': c => c.name === 'baz',
115+
'the second filtered cookie value should be 44': c => c.value === '44',
116+
});
117+
118+
// clear cookies
119+
await context.clearCookies();
120+
await check(await context.cookies(), {
121+
'number of cookies should be zero': c => c.length === 0,
122+
});
123+
} finally {
124+
await page.close();
125+
}
126+
}

examples/browser/device_emulation.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { browser, devices } from 'k6/browser';
2+
import { check } from 'https://jslib.k6.io/k6-utils/1.5.0/index.js';
3+
4+
export const options = {
5+
scenarios: {
6+
ui: {
7+
executor: 'shared-iterations',
8+
options: {
9+
browser: {
10+
type: 'chromium',
11+
},
12+
},
13+
},
14+
},
15+
thresholds: {
16+
checks: ["rate==1.0"]
17+
}
18+
}
19+
20+
export default async function() {
21+
const device = devices['iPhone X'];
22+
// The spread operator is currently unsupported by k6's Babel, so use
23+
// Object.assign instead to merge browser context and device options.
24+
// See https://github.com/grafana/k6/issues/2296
25+
const options = Object.assign({ locale: 'es-ES' }, device);
26+
const context = await browser.newContext(options);
27+
const page = await context.newPage();
28+
29+
try {
30+
await page.goto('https://test.k6.io/', { waitUntil: 'networkidle' });
31+
const dimensions = await page.evaluate(() => {
32+
return {
33+
width: document.documentElement.clientWidth,
34+
height: document.documentElement.clientHeight,
35+
deviceScaleFactor: window.devicePixelRatio
36+
};
37+
});
38+
39+
await check(dimensions, {
40+
'width': d => d.width === device.viewport.width,
41+
'height': d => d.height === device.viewport.height,
42+
'scale': d => d.deviceScaleFactor === device.deviceScaleFactor,
43+
});
44+
45+
if (!__ENV.K6_BROWSER_HEADLESS) {
46+
await page.waitForTimeout(10000);
47+
}
48+
} finally {
49+
await page.close();
50+
}
51+
}

examples/browser/dispatch.js

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { browser } from 'k6/browser';
2+
import { check } from 'https://jslib.k6.io/k6-utils/1.5.0/index.js';
3+
4+
export const options = {
5+
scenarios: {
6+
ui: {
7+
executor: 'shared-iterations',
8+
options: {
9+
browser: {
10+
type: 'chromium',
11+
},
12+
},
13+
},
14+
},
15+
thresholds: {
16+
checks: ["rate==1.0"]
17+
}
18+
}
19+
20+
export default async function() {
21+
const context = await browser.newContext();
22+
const page = await context.newPage();
23+
24+
try {
25+
await page.goto('https://test.k6.io/', { waitUntil: 'networkidle' });
26+
27+
const contacts = page.locator('a[href="/contacts.php"]');
28+
await contacts.dispatchEvent("click");
29+
30+
await check(page.locator('h3'), {
31+
'header': async lo => {
32+
const text = await lo.textContent();
33+
return text == 'Contact us';
34+
}
35+
});
36+
} finally {
37+
await page.close();
38+
}
39+
}

0 commit comments

Comments
 (0)