Grafana k6 Browser


Grafana k6 Browser current version
k6 Browser 在設計時, 大部分 API 跟名詞都遵循 Playwright 的 API 設計, 這樣的設計使得開發者降低從 Playwright 遷移腳本的認知負擔, 還可直接重用現有的元素定位策略. 以及未來會支援 Playwright RPC server .
瀏覽器的部分現在只支援Chromium
, 未來才會陸續支援 Firefox
和 WebKit-based
的瀏覽器.
所以 k6 Browser 若是有看不懂或描述不清楚的文件, 前往 Playwright 官網查看會比較快又豐富.
k6 Browser 常用 API
k6 Browser 提供了很多 API 模組,其中許多都跟 Playwright 的操作方式兼容。以下是一些基本一定會用到的 API 模組。
Browser context
BrowserContext 在 Playwright 中是一個重要的概念,它代表了一個獨立的瀏覽器 session,類似於 private 模式,可以隔離 cookies 和 Local storage 等,使得每個測試案例在獨立的環境中運行,避免相互幹擾。
常見測試目的像是多帳號測試場景
、不同裝置或效能測試場景的模擬
等。
import { browser } from "k6/browser";
const iphoneX = devices['iPhone X'];
const context = await browser.newContext(iphoneX);
Page
在 Playwright 和 k6 中,**Page**代表一個瀏覽器標籤頁或頁面,使用者透過Page物件與頁面內容交互,例如導覽、元素操作等。需要區分不同框架中的實作差異,例如 k6 可能更注重效能測試,而 Playwright 則更著重功能測試。同一個 BrowserContext 底下能開啟很多 Page 來測試。
// 多頁面管理範例
const context = browser.newContext();
const page1 = await context.newPage();
const page2 = await context.newPage();
// 頁面關係示意
Browser Instance
└── BrowserContext
├── Page 1 (Main Frame)
│ └── Sub Frame (iframe)
└── Page 2 (SPA)
如果有多個頁面,我們能實踐**跨 Page 通訊**的測試互動
// 主頁面
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.click('#open-window')
]);
// 新頁面操作
await popup.fill('#email', 'test@example.com');
但絕大多數我們幾乎都是在單個 Page 上操作的,畢竟現在大部份網站都是 SPA 架構了。
goto
與 waitForNavigation
是 page 蠻常會使用的 API。
將該頁導航至對應的 URL。通常測試腳本一開始就會執行這句。
await page.goto(`https://otel-demo.field-eng.grafana.net/`);
waitForNavigation([options])
在很多範例的測試腳本都會看到 page.waitForNavigation()
,用來等待頁面導航至新的 URL 或重新載入。當您執行會間接導致頁面導航的程式碼時,此方法非常有用。
如果不知道這隻 API,大家肯定會用 sleep(t)
或者使用 waitForTimeout(t)
,但這個會是個反模式。最顯著的問題是 life cycle,sleep 本身就是blocking的操作,所以 sleep的時長就會被納入 k6 的統計中。其次是可能會使得測試不夠穩定,也可能 sleep 過了,頁面都還沒載入。而waitForTimeout
跟 sleep 其實是一樣的東西。
page.waitForNavigation()
頁面載入狀態事件
當一個頁面開始載入時,有三個狀態我們需要了解的。domcontentloaded
、load
和 networkidle
,接下來,我需要分別解釋這三個事件:
domcontentloaded:當DOMContentLoaded事件觸發時完成操作。這個事件在HTML文件被完全載入和解析時觸發,無需等待 CSS、image 和框架的載入。適用於需要快速與DOM互動的情況,例如測試頁面結構是否已載入完成。
load(預設值):當頁面所有資源(如圖片、CSS)載入完成後觸發 load 事件。這會等待更長時間,適合需要所有資源就緒的場景,例如測試頁面完全載入後的功能。
networkidle:網路空閒至少500毫秒時認為完成。但用戶指出這在某些情況下可能不可靠,特別是對於持續有網路請求的網站(如聊天應用程式)。 k6 browser可能同樣存在這個問題,因此建議使用明確的斷言來確認頁面狀態。
事件狀態比較表
事件類型 | 觸發時機 | 適用場景 | 潛在風險 |
domcontentloaded | HTML 解析完成,DOM 樹構建完畢 | 表單驗證、DOM 操作測試 | 樣式未載入可能影響佈局判斷 |
load | 所有資源(圖片/CSS/JS)載入完成 | 完整頁面渲染驗證 | 第三方資源延遲導致超時 |
networkidle | 連續 500ms 無網絡活動 | 傳統 SPA 應用 | 長輪詢/WebSocket 導致失效 |
sequenceDiagram
participant 瀏覽器
participant 測試腳本
瀏覽器->>瀏覽器: 解析 HTML (DOMContentLoaded)
瀏覽器->>瀏覽器: 載入外部資源 (load)
瀏覽器->>瀏覽器: 監控網絡活動 (networkidle)
瀏覽器->>測試腳本: 觸發 ready 狀態
screenshot([options])
將該頁面給截圖並且儲存在指定路徑,這在 debug 很好用。
await page.screenshot({ path: `./screenshots/order.png` });
Evaluate
evaluate允許在瀏覽器上下文中執行 JavaScript程式碼,通常用於取得或操作頁面元素,或執行特定的客戶端邏輯,如操作 DOM 元素、獲取頁面資料並操作、執行複雜計算「跟頁面中的 JavaScript 互動,甚至能注入 JavaScript 程式等,並且把結果返回 k6 中。且 evaluate 能處理 sync 和 async 函數,並向這些函數傳遞參數。
像是透過 evaluate 執行 window.performance.mark 或是 window.performance.measure,用於精確測量程式碼區塊的效能,幫助開發者分析執行時間和最佳化效能。但其實 k6 browser metrics 有幫忙監測這些數值。
const result = await page.evaluate("([x, y]) => Promise.resolve(x * y)", [7,8]);
console.log(`Result: ${result}`);
await page.evaluate(() => window.performance.mark('page-visit'));
// 假設頁面中有 greet(content) 的 JavaScript 函數
result = await page.evaluate('greet("k6 demo")');
Locator
Locator 絕對是這裡面最需要搞懂的模組,它與document.querySelector
ㄧ 樣就是找到需要操作的元素。
locator(selector)
唯一的參數就是 selector,這有常在使用 jQuery 的開發者應該不陌生了 XD
關於 select 的選擇官方有給一份最佳實踐。
盡可能優先選擇頁面裡唯一的屬性。
穩定性,抗變更的能力
語意性,團隊中的規範
graph TD
A[選擇器品質] --> B[唯一性]
A --> C[穩定性]
A --> D[語意性]
B --> E[DOM 層級唯一驗證]
C --> F[架構抗變更能力]
D --> G[團隊溝通效率]
還給了評分表當作參考 ❌ = 0, ⚠️= 0.5, ✅ = 1 ,選用的 selector 類型分數越高越好。
Unique to the page | Value is stable | Conveys intent | Total | |
autocapitalize | ❌ | ❌ | ❌ | 0 |
class | ❌ | ⚠️ | ⚠️ | 1 |
id | ✅ | ⚠️ | ⚠️ | 2 |
name | ✅ | ⚠️ | ✅ | 2.5 |
placeholder | ✅ | ⚠️ | ✅ | 2.5 |
data-testid | ✅ | ✅ | ✅ | 3 |
// 危險範例
page.locator('.css-8tk2dk-input-input') // 由CSS-in-JS生成
pie
title Class選擇器失敗原因
"非唯一性" : 45
"編譯工具修改" : 30
"重構被刪除" : 20
"語意不明" : 5
// React產生的易變ID
page.locator('#:r0:'); // 使用useId()生成
// 多語言情境下的問題
page.locator('[placeholder="email or username"]')
selector 若選用 placeholder 的話可能會有以下問題︰
需要與i18n系統整合
內容團隊可能修改文案
無法區分相似元素(如多個email輸入框)
data-testid
這我其實不知道是什麼,我看公司平台的前端內容是有 data-pc-section、data-pc-nam,就加減用了。真的沒招的話,最差就是 XPath
了。
page.locator(`//h2[text()="Product Title"]`)
Check
若是以前有再寫 k6 API 測試的開發者,一定對 check 不陌生。以前的話是這樣寫
check( val, sets, [tags] )
,set 則是對 val 做斷言檢查的部份。
import { check } from 'k6';
import http from 'k6/http';
export default function () {
const res = http.get('http://test.k6.io/');
check(res, {
'is status 200': (r) => r.status === 200,
});
}
But ! 瀏覽器的元件都是 Asynchronous 的。原本 k6 內建的 check 沒辦法處理。因此需要 k6 util 提供的 check 來處理非同步事件。將 locator 取得後,來斷言其內容或狀態。
import { check } from 'https://jslib.k6.io/k6-utils/1.6.0/index.js';
export default async function () {
const page = await browser.newPage();
try {
await page.goto('https://test.k6.io/my_messages.php');
await check(page.locator('h2'), {
'header': async lo => await lo.textContent() == 'Welcome, admin!'
});
} finally {
await page.close();
}
}
有 check 的話,都會在 options 的 thresholds 搭配使用 checks 來檢查失敗比例需要高於多少。以下範例就是全部 check 的成功率需要超過 9 成。rate 的範圍值在 [0.0, 1.0] 之間,1.0
等於就是 100% check 都成功。
export const options = {
thresholds: {
// the rate of successful checks should be higher than 90%
checks: ['rate>0.9'],
},
};
graph TD
A[測試執行] --> B[進行檢查]
B --> C{檢查結果}
C -->|成功| D[成功計數+1]
C -->|失敗| E[失敗計數+1]
D & E --> F[計算成功率]
F --> G[成功率 >= 1.0 ?]
G -->|是| H[標記為通過]
G -->|否| I[標記為失敗]
Executor ︰Shared-iterations
k6 其實提供很多 Executor,但 UI 測試幾乎都選用 Shared-iterations 居多,我們先理解 shared-iterations 是什麼。Executor 的選擇主要會根據我們執行測試腳本時,對於設定好的 VU 數量,想要怎麼調度的策略。k6 目前提供了 7 種VU調度策略。有興趣能自己研究。
shared-iterations executor 主要策略是讓設定好的 VU數量去共享迭代次數。以下範例就是 10 個 VU 去共享200次的腳本迭代,但因為執行時間不是穩定的,每個 VU 實際跑到的次數不一定的。
export const options = {
scenarios: {
contacts: {
executor: 'shared-iterations',
vus: 10,
iterations: 200,
},
},
};
而在 UI 的測試中,其實就只會設定一個 VU 跑一次迭代而已。除非有必要才會設定其他的參數。
OTel Demo 電商專案 Example
import { browser } from "k6/browser";
import { check } from "https://jslib.k6.io/k6-utils/1.6.0/index.js";
import { sleep, fail } from "k6";
const LESS_IMPORTANT = `info`;
const TWO_SECONDS = 2000;
export const options = {
scenarios: {
ui: {
executor: "shared-iterations",
options: {
browser: {
type: "chromium",
},
},
},
},
thresholds: {
'browser_web_vital_lcp': ['p(90) < 1500'],
'browser_web_vital_inp': ['p(90) < 1500'],
//'browser_web_vital_inp{url:https://otel-demo.field-eng.grafana.net/}': ['p(90) < 1500'],
'browser_http_req_failed': ['rate < 0.3'],
checks: ['rate==1.0'],
},
};
export default async function () {
const context = await browser.newContext();
const page = await context.newPage();
const result = await page.evaluate("([x, y]) => Promise.resolve(x * y)", [7,8]);
console.log(`Result: ${result}`);
// await page.waitForTimeout(5000);
await page.goto(`https://otel-demo.field-eng.grafana.net/`);
// await page.evaluate(() => window.performance.mark('page-visit'));
// Check homepage title
check(await page.title(), {
"Homepage title is correct": (title) => title.includes("OTel demo"),
});
await page.locator(`//*[text()="Go Shopping"]`).click();
// Check product listing page
check(await page.url(), {
"URL changed to products page": (url) => url.includes("/#hot-products"),
});
await Promise.all([
page
.locator(`//*[text()="Starsense Explorer Refractor Telescope"]`)
.click(),
// sleep (3000);
page.waitForNavigation(),
]);
// Check product details page
check(await page.url(), {
'URL changed to product detail page': (url) => url.includes('/product/')
});
// Check product price is present
const priceElement = await page.locator('[data-cy="product-price"]');
check(await priceElement.isVisible(), {
'Product price is visible': (isVisible) => isVisible === true
});
// less important check
await checkForRecommendedProducts(page, `Product page`);
await Promise.all([
page.locator(`//*[text()=" Add To Cart"]`).click(),
page.waitForNavigation(),
]);
// Check cart page
check(await page.url(), {
'URL changed to cart page': (url) => url.includes('/cart')
});
await page.locator("#email").fill("nathan@demo.com");
await page.screenshot({ path: `./screenshots/order.png` });
await Promise.all([
page.locator(`//*[text()="Place Order"]`).click(),
page.waitForNavigation(),
]);
// Check order checkout page
check(await page.url(), {
'URL changed to order checkout page': (url) => url.includes('/checkout')
});
// less important check
await checkForRecommendedProducts(page, `Order confirmation`);
await page.close();
}
async function checkForRecommendedProducts(page, step) {
try {
await page
.locator(
`[data-cy="recommendation-list"] [data-cy="product-card"]:first-of-type`
)
.waitFor({ timeout: TWO_SECONDS });
} catch (e) {
await page.screenshot({ path: `./screenshots/${step}.png` });
fail(
`Failed to find recommended products on ${step} page within ${TWO_SECONDS}ms timeout`
);
} finally {
const cards = await page.$$(`[data-cy="product-card"]`);
console.log(step, cards.length);
check(
cards.length,
{
"4 recommended products are displayed": (length) => length === 4,
},
{
importance: LESS_IMPORTANT,
}
);
}
}
Browser Metrics
> K6_BROWSER_HEADLESS=false k6 run demo1.js
/\ Grafana /‾‾/
/\ / \ |\ __ / /
/ \/ \ | |/ / / ‾‾\
/ \ | ( | (‾) |
/ __________ \ |_|\_\ \_____/
execution: local
script: demo1.js
output: -
scenarios: (100.00%) 1 scenario, 1 max VUs, 10m30s max duration (incl. graceful stop):
* ui: 1 iterations shared among 1 VUs (maxDuration: 10m0s, gracefulStop: 30s)
INFO[0000] Result: 56 source=console
INFO[0004] Product page 4 source=console
INFO[0006] Order confirmation 4 source=console
✓ Homepage title is correct
✓ URL changed to products page
✓ URL changed to product detail page
✓ Product price is visible
✓ 4 recommended products are displayed
✓ URL changed to cart page
✓ URL changed to order checkout page
browser_data_received.......: 12 MB 1.9 MB/s
browser_data_sent...........: 158 kB 26 kB/s
browser_http_req_duration...: avg=216.35ms min=49.64ms med=209.6ms max=392.39ms p(90)=242.57ms p(95)=300.16ms
✓ browser_http_req_failed.....: 2.94% 2 out of 68
browser_web_vital_cls.......: avg=0.027803 min=0.027803 med=0.027803 max=0.027803 p(90)=0.027803 p(95)=0.027803
browser_web_vital_fcp.......: avg=563ms min=563ms med=563ms max=563ms p(90)=563ms p(95)=563ms
browser_web_vital_fid.......: avg=199.99µs min=199.99µs med=199.99µs max=199.99µs p(90)=199.99µs p(95)=199.99µs
✓ browser_web_vital_inp.......: avg=136ms min=136ms med=136ms max=136ms p(90)=136ms p(95)=136ms
✓ browser_web_vital_lcp.......: avg=1.16s min=1.16s med=1.16s max=1.16s p(90)=1.16s p(95)=1.16s
browser_web_vital_ttfb......: avg=301.5ms min=301.5ms med=301.5ms max=301.5ms p(90)=301.5ms p(95)=301.5ms
✓ checks......................: 100.00% 8 out of 8
data_received...............: 0 B 0 B/s
data_sent...................: 0 B 0 B/s
iteration_duration..........: avg=5.81s min=5.81s med=5.81s max=5.81s p(90)=5.81s p(95)=5.81s
iterations..................: 1 0.164587/s
vus.........................: 1 min=1 max=1
vus_max.....................: 1 min=1 max=1
running (00m06.1s), 0/1 VUs, 1 complete and 0 interrupted iterations
ui ✓ [======================================] 1 VUs 00m06.1s/10m0s 1/1 shared iters
Google Core Web Vitals,包括 CLS、FID、LCP等。這些其實我也沒很懂。
LCP(Largest Contentful Paint)大概是說主要內容加載,然後使用者多快能看到頁面主題。
FID(First Input Delay),使用者首次能互動的時間(能點擊/輸入)。
CLS (Cumulative Layout Shift)頁面視覺穩定性…我不懂。
INP(Interaction to Next Paint)複雜互動的回應速度。
FCP(First Contentful Paint)跟TTDB(Time to First Byte)
我個人主要是檢查 browser_http_req_failed
跟 checks failed rate
。Core Web Vitals 我也不怎懂 XD
此外,k6 的這些指標也能通過 OpenTelemetry 輸出到遠端的服務裡來儲存分析。
K6_OTEL_GRPC_EXPORTER_INSECURE=true K6_OTEL_METRIC_PREFIX=k6_ k6 run -o experimental-opentelemetry demo1.js
或是參考小弟出版的OpenTelemetry 入門指南︰建立全面可觀測性架構 Ch 13,透過 Prometheus remote write 寫進 Prometheus 中。
Hybrid Testing
有用過 k6 scenarios 的開發者就知道,其實我們能再 scenarios
中定義多個 scenario 然後指定 exec
的函數。所以也能應用這樣的方式,同時對 UI 和 API 做不同目的的測試。這裡就用到另一種 constant-vus executor,所以你執行後會看到同時有3個瀏覽氣在執行。
import { browser } from "k6/browser"
import http from "k6/http"
import { check } from "https://jslib.k6.io/k6-utils/1.6.0/index.js"
const PRODUCT_IDS = __ENV.PRODUCT_IDS
const HAS_SOME_LEEWAY = `warn`
const SUPER_IMPORTANT_CHECK = `critical`
const LESS_IMPORTANT = `info`
export const options = {
scenarios: {
ui: {
executor: "constant-vus",
duration: "1m",
vus: 3,
options: {
browser: {
type: "chromium",
},
},
exec: "checkoutCompletion",
},
"spike-api": {
executor: "ramping-vus",
startVUs: 0,
stages: [
{ duration: "10s", target: 10 },
{ duration: "40s", target: 30 },
{ duration: "10s", target: 10 },
],
gracefulRampDown: "10s",
exec: "spikeApi",
},
},
thresholds: {
[`checks{importance:${SUPER_IMPORTANT_CHECK}}`]: ["rate==1.0"],
[`checks{importance:${HAS_SOME_LEEWAY}}`]: ["rate>=0.95"],
[`checks{importance:${LESS_IMPORTANT}}`]: ["rate>=0.9"],
},
}
export function spikeApi() {
const randomProduct =
PRODUCT_IDS[Math.floor(Math.random() * PRODUCT_IDS.length)]
const res = http.get(`https://otel-demo.field-eng.grafana.net/api/recommendations?productIds=${randomProduct}`)
check(
res,
{
"status code is 200": (r) => r.status === 200,
},
{ importance: HAS_SOME_LEEWAY }
)
}
export async function checkoutCompletion() {
const context = await browser.newContext()
const page = await context.newPage()
await page.goto(`https://otel-demo.field-eng.grafana.net/`)
await page.locator(`//*[text()="Go Shopping"]`).click()
await Promise.all([
page
.locator(`//*[text()="Starsense Explorer Refractor Telescope"]`)
.click(),
page.waitForNavigation(),
])
// less important check
await checkForRecommendedProducts(page, `Product page`)
await Promise.all([
page.locator(`//*[text()=" Add To Cart"]`).click(),
page.waitForNavigation(),
])
// less important check
await checkForRecommendedProducts(page, `Shipping form`)
await Promise.all([
page.locator(`//*[text()="Place Order"]`).click(),
page.waitForNavigation(),
])
// Super important check
await check(
page.locator(`h1`),
{
"Place order page was reached": async (lo) =>
(await lo.textContent()) === "Your order is complete!",
},
{ important: SUPER_IMPORTANT_CHECK }
)
// less important check
await checkForRecommendedProducts(page, `Order confirmation`)
await page.close()
}
const TWO_SECONDS = 2000
async function checkForRecommendedProducts(page, step) {
try {
await page
.locator(
`[data-cy="recommendation-list"] [data-cy="product-card"]:first-of-type`
)
.waitFor({ timeout: TWO_SECONDS })
} catch (e) {
await page.screenshot({ path: `./screenshots/${step}.png` })
} finally {
const cards = await page.$$(`[data-cy="product-card"]`)
console.log(step, cards.length)
check(
cards.length,
{
"4 recommended products are displayed": (length) => length === 4,
},
{
importance: LESS_IMPORTANT,
}
)
}
}
能看到以下測試結果,我測試到一半時, server 其實就異常了。但無礙,因為是 hybrid testing,所以 Browser metrics 與 Http metrics 都有被計算出來了。這就是 Playwright 無法提供的,畢竟 k6 能提供很完整且強大的 metrics 資料以及各種負載測試類型的測試策略。
> K6_BROWSER_HEADLESS=false k6 run demo2.js -e PRODUCT_IDS=10
/\ Grafana /‾‾/
/\ / \ |\ __ / /
/ \/ \ | |/ / / ‾‾\
/ \ | ( | (‾) |
/ __________ \ |_|\_\ \_____/
execution: local
script: demo5.js
output: -
scenarios: (100.00%) 2 scenarios, 33 max VUs, 1m30s max duration (incl. graceful stop):
* spike-api: Up to 30 looping VUs for 1m0s over 3 stages (gracefulRampDown: 10s, exec: spikeApi, gracefulStop: 30s)
* ui: 3 looping VUs for 1m0s (exec: checkoutCompletion, gracefulStop: 30s)
INFO[0002] Product page 4 source=console
INFO[0003] Shipping form 4 source=console
INFO[0003] Order confirmation 4 source=console
INFO[0004] Product page 4 source=console
INFO[0004] Product page 4 source=console
INFO[0005] Shipping form 4 source=console
INFO[0005] Shipping form 4 source=console
INFO[0005] Order confirmation 4 source=console
INFO[0006] Order confirmation 4 source=console
INFO[0008] Product page 4 source=console
INFO[0008] Product page 4 source=console
INFO[0010] Product page 0 source=console
INFO[0011] Shipping form 4 source=console
INFO[0012] Shipping form 4 source=console
INFO[0012] Order confirmation 4 source=console
INFO[0013] Order confirmation 4 source=console
INFO[0015] Product page 4 source=console
INFO[0016] Shipping form 4 source=console
INFO[0016] Product page 4 source=console
INFO[0017] Order confirmation 4 source=console
INFO[0017] Shipping form 4 source=console
INFO[0017] Order confirmation 4 source=console
INFO[0021] Product page 4 source=console
INFO[0022] Product page 4 source=console
INFO[0022] Shipping form 4 source=console
INFO[0022] Shipping form 4 source=console
INFO[0022] Order confirmation 4 source=console
INFO[0023] Order confirmation 4 source=console
INFO[0026] Product page 4 source=console
INFO[0026] Shipping form 4 source=console
INFO[0027] Product page 4 source=console
INFO[0027] Order confirmation 4 source=console
INFO[0027] Shipping form 4 source=console
INFO[0028] Order confirmation 4 source=console
INFO[0029] Product page 4 source=console
INFO[0030] Shipping form 4 source=console
INFO[0031] Order confirmation 4 source=console
INFO[0033] Product page 4 source=console
INFO[0033] Shipping form 4 source=console
INFO[0035] Order confirmation 4 source=console
INFO[0035] Product page 4 source=console
INFO[0035] Shipping form 4 source=console
INFO[0036] Order confirmation 4 source=console
ERRO[0038] Uncaught (in promise) waiting for navigation: timed out after 30s executor=constant-vus scenario=ui
INFO[0041] Product page 0 source=console
INFO[0041] Product page 0 source=console
WARN[0069] Unexpected DevTools server error: context canceled category="ExecutionContext:eval" elapsed="0 ms" source=browser
ERRO[0069] Uncaught (in promise) clicking on "//*[text()=\"Go Shopping\"]": timed out after 30s executor=constant-vus scenario=ui
WARN[0071] Unexpected DevTools server error: context canceled category="ExecutionContext:eval" elapsed="0 ms" source=browser
WARN[0071] Unexpected DevTools server error: context canceled category="ExecutionContext:eval" elapsed="0 ms" source=browser
ERRO[0071] Uncaught (in promise) clicking on "//*[text()=\" Add To Cart\"]": timed out after 30s executor=constant-vus scenario=ui
ERRO[0071] Uncaught (in promise) clicking on "//*[text()=\" Add To Cart\"]": timed out after 30s executor=constant-vus scenario=ui
✗ status code is 200
↳ 75% — ✓ 2059 / ✗ 671
✗ 4 recommended products are displayed
↳ 93% — ✓ 42 / ✗ 3
✗ Place order page was reached
↳ 78% — ✓ 11 / ✗ 3
browser_data_received..........: 162 MB 2.3 MB/s
browser_data_sent..............: 2.6 MB 36 kB/s
browser_http_req_duration......: avg=243.14ms min=50µs med=218.64ms max=2.5s p(90)=308.08ms p(95)=390.47ms
browser_http_req_failed........: 4.31% 48 out of 1113
browser_web_vital_cls..........: avg=0.052306 min=0.026286 med=0.027803 max=0.23987 p(90)=0.11228 p(95)=0.23917
browser_web_vital_fcp..........: avg=592.86ms min=244ms med=522.5ms max=2.45s p(90)=589.2ms p(95)=663.5ms
browser_web_vital_fid..........: avg=1.78ms min=199.99µs med=1.59ms max=3.59ms p(90)=3.43ms p(95)=3.51ms
browser_web_vital_inp..........: avg=107.29ms min=48ms med=96ms max=216ms p(90)=144ms p(95)=177.59ms
browser_web_vital_lcp..........: avg=1.15s min=658.29ms med=1.2s max=2.45s p(90)=1.31s p(95)=1.49s
browser_web_vital_ttfb.........: avg=349.74ms min=200.6ms med=238.59ms max=2.41s p(90)=293.4ms p(95)=306.4ms
checks.........................: 75.72% 2112 out of 2789
✓ { importance:critical }......: 0.00% 0 out of 0
✓ { importance:info }..........: 93.33% 42 out of 45
✗ { importance:warn }..........: 75.42% 2059 out of 2730
data_received..................: 5.9 MB 83 kB/s
data_sent......................: 258 kB 3.6 kB/s
http_req_blocked...............: avg=117.56µs min=200ns med=491ns max=18.19ms p(90)=671ns p(95)=772ns
http_req_connecting............: avg=32.99µs min=0s med=0s max=3.6ms p(90)=0s p(95)=0s
http_req_duration..............: avg=385.08ms min=196.1ms med=246.84ms max=3.3s p(90)=703.54ms p(95)=991.39ms
{ expected_response:true }...: avg=343.25ms min=208.15ms med=254.68ms max=2.42s p(90)=536.31ms p(95)=709.43ms
http_req_failed................: 24.57% 671 out of 2730
http_req_receiving.............: avg=158.17µs min=11.23µs med=137.2µs max=8.76ms p(90)=238.37µs p(95)=283.66µs
http_req_sending...............: avg=40.71µs min=11.82µs med=38.86µs max=163.39µs p(90)=56.8µs p(95)=64.05µs
http_req_tls_handshaking.......: avg=79.32µs min=0s med=0s max=14.87ms p(90)=0s p(95)=0s
http_req_waiting...............: avg=384.88ms min=195.99ms med=246.56ms max=3.3s p(90)=703.43ms p(95)=991.29ms
http_reqs......................: 2730 38.299487/s
iteration_duration.............: avg=457.94ms min=196.26ms med=248.53ms max=36.02s p(90)=725.73ms p(95)=1.18s
iterations.....................: 2748 38.552011/s
vus............................: 2 min=2 max=32
vus_max........................: 33 min=33 max=33
running (1m11.3s), 00/33 VUs, 2748 complete and 0 interrupted iterations
spike-api ✓ [======================================] 00/30 VUs 1m0s
ui ✓ [======================================] 3 VUs 1m0s
ERRO[0071] thresholds on metrics 'checks{importance:warn}' have been crossed
References
Grafana Blog - 5 tips to write better browser tests for performance testing and synthetic monitoring
Grafana Blog - Frontend vs. backend: How to plan your performance testing strategy
Grafana Blog - How to load test a website: A comprehensive guide
Subscribe to my newsletter
Read articles from 雷N directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
