菜单
开源

使用 k6 browser 实现混合性能

一种替代 基于浏览器的负载测试 的方法是,将少量虚拟用户用于浏览器测试,同时使用大量虚拟用户进行协议级测试,这种方法对资源的要求低得多。

你可以通过多种方式实现混合性能,通常是使用不同的工具。为了简化开发者体验,你可以将 k6 browser 与核心 k6 功能结合,在单个脚本中编写混合测试。

浏览器和 HTTP 测试

下面的代码展示了在单个脚本中结合浏览器和 HTTP 测试的示例。该脚本在让后端承受典型负载的同时,还检查前端是否存在任何意外问题。它还定义了阈值,用于根据预定义的 SLO 检查 HTTP 和浏览器指标。

JavaScript
import { browser } from 'k6/browser';
import { check } from 'https://jslib.k6.io/k6-utils/1.5.0/index.js';
import { randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
import http from 'k6/http';

const BASE_URL = __ENV.BASE_URL || 'https://quickpizza.grafana.com';

export const options = {
  scenarios: {
    load: {
      exec: 'getPizza',
      executor: 'ramping-vus',
      stages: [
        { duration: '5s', target: 5 },
        { duration: '10s', target: 5 },
        { duration: '5s', target: 0 },
      ],
      startTime: '10s',
    },
    browser: {
      exec: 'checkFrontend',
      executor: 'constant-vus',
      vus: 1,
      duration: '30s',
      options: {
        browser: {
          type: 'chromium',
        },
      },
    },
  },
  thresholds: {
    http_req_failed: ['rate<0.01'],
    http_req_duration: ['p(95)<500', 'p(99)<1000'],
    browser_web_vital_fcp: ['p(95) < 1000'],
    browser_web_vital_lcp: ['p(95) < 2000'],
  },
};

export function getPizza() {
  const restrictions = {
    maxCaloriesPerSlice: 500,
    mustBeVegetarian: false,
    excludedIngredients: ['pepperoni'],
    excludedTools: ['knife'],
    maxNumberOfToppings: 6,
    minNumberOfToppings: 2,
  };

  const res = http.post(`${BASE_URL}/api/pizza`, JSON.stringify(restrictions), {
    headers: {
      'Content-Type': 'application/json',
      'X-User-ID': randomIntBetween(1, 30000),
    },
  });

  check(res, {
    'status is 200': (res) => res.status === 200,
  });
}

export async function checkFrontend() {
  const page = await browser.newPage();

  try {
    await page.goto(BASE_URL);

    await check(page.locator('h1'), {
      'header': async lo => await lo.textContent() == 'Looking to break out of your pizza routine?'
    });

    await Promise.all([
      page.locator('//button[. = "Pizza, Please!"]').click(),
      page.waitForTimeout(500),
    ]);
    await page.screenshot({ path: `screenshots/${__ITER}.png` });

    await check(page.locator('div#recommendations'), {
      'recommendation': async lo => await lo.textContent() != '',
    });
  } finally {
    await page.close();
  }
}

如果你将该脚本保存到名为 test.js 的本地文件,你可以使用以下命令运行它:

bash
k6 run test.js

该脚本还包含了一个常见的最佳实践:定义 BASE_URL 变量,并使用(如果存在的话)环境变量__ENV.BASE_URL。如果你想对多个环境(例如预发布和生产环境)使用同一个脚本,这会非常有用,你可以通过以下命令将该值传递给脚本:

bash
k6 run -e BASE_URL=https://quickpizza.grafana.com test.js

浏览器和故障注入测试

你还可以通过使用 xk6-disruptor 扩展,将浏览器测试与故障注入测试结合运行。这种方法可以帮助你发现前端问题,例如当前端依赖的任何服务突然被注入延迟或服务器错误等故障时。

以下代码展示了如何使用 xk6-disruptor 扩展向 Kubernetes 服务引入故障的示例。同时,browser 场景会运行,以确保前端应用程序没有未得到妥善处理的意外错误。

要了解有关向服务注入故障的更多信息,请查阅xk6-disruptor 入门指南

JavaScript
import { browser } from 'k6/browser';
import { check } from 'https://jslib.k6.io/k6-utils/1.5.0/index.js';
import { ServiceDisruptor } from 'k6/x/disruptor';

const BASE_URL = __ENV.BASE_URL;

export const options = {
  scenarios: {
    disrupt: {
      executor: 'shared-iterations',
      iterations: 1,
      vus: 1,
      exec: 'disrupt',
    },
    browser: {
      executor: 'constant-vus',
      vus: 1,
      duration: '10s',
      startTime: '10s',
      exec: 'browser',
      options: {
        browser: {
          type: 'chromium',
        },
      },
    },
  },
  thresholds: {
    browser_web_vital_fcp: ['p(95) < 1000'],
    browser_web_vital_lcp: ['p(95) < 2000'],
  },
};

// Add faults to the service by introducing a delay of 1s and 503 errors to 10% of the requests.
const fault = {
  averageDelay: '1000ms',
  errorRate: 0.1,
  errorCode: 503,
};

export function disrupt() {
  const disruptor = new ServiceDisruptor('pizza-info', 'pizza-ns');
  const targets = disruptor.targets();
  if (targets.length == 0) {
    throw new Error('expected list to have one target');
  }

  disruptor.injectHTTPFaults(fault, '20s');
}

export async function checkFrontend() {
  const page = await browser.newPage();

  try {
    await page.goto(BASE_URL);
    await check(page.locator('h1'), {
      'header': async lo => await lo.textContent() == 'Looking to break out of your pizza routine?'
    });

    await Promise.all([
      page.locator('//button[. = "Pizza, Please!"]').click(),
      page.waitForTimeout(500),
    ]);
    await page.screenshot({ path: `screenshots/${__ITER}.png` });

    await check(page.locator('div#recommendations'), {
      recommendation: async lo => await lo.textContent() != '',
    });
  } finally {
    await page.close();
  }
}
  • 从小规模开始。从少量基于浏览器的虚拟用户开始。一个好的起点是使用 10% 或更少的虚拟用户来监控终端用户的用户体验,同时脚本模拟约 90% 的协议级流量。
  • 将浏览器测试与不同负载测试类型结合。为了充分了解不同流量模式对终端用户体验的影响,请尝试将你的浏览器测试与不同的负载测试类型结合运行。
  • 先关注高风险用户旅程。首先确定高风险用户旅程,以便在后端应用承受高流量或服务故障时,开始监控它们相关的 Web 性能指标。