grafana / xk6-browser

k6 extension that adds support for browser automation and end-to-end web testing via the Chrome Devtools Protocol
https://grafana.com/docs/k6/latest/javascript-api/k6-experimental/browser/
GNU Affero General Public License v3.0
337 stars 42 forks source link

panic: interface conversion: interface {} is nil, not *common.ElementHandle #53

Closed tom-miseur closed 2 years ago

tom-miseur commented 2 years ago

The following script causes panic: interface conversion: interface {} is nil, not *common.ElementHandle during the call to page.waitForSelector("li.design-tile");:

import { sleep, group } from 'k6';
import launcher from 'k6/x/browser';

import { randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.1.0/index.js';

export const options = {};

let browser, context, page;

const pauseMin = 5;
const pauseMax = 5;

export default function() {
  initialize();

  navigateToHomepage();
  clickBusinessCards();
  clickBrowseDesigns();
  clickDesign('Standard (3.5" x 2")');
}

function initialize() {
  browser = launcher.launch('chromium', {
    args: [],
    debug: false,
    devtools: false,
    env: {},
    executablePath: null,
    headless: false,
    ignoreDefaultArgs: [],
    proxy: {},
    slowMo: '500ms',
    timeout: '30s',
  });

  context = browser.newContext({
    ignoreHTTPSErrors: true
  });

  page = context.newPage();
}

function navigateToHomepage() {
  console.log('Starting navigateToHomepage...');

  group("Navigate to Homepage", () => {
    page.goto('https://vistaprint.ca', { waitUntil: 'networkidle' });

    page.waitForSelector(".standard-hero-text");
  });

  sleep(randomIntBetween(pauseMin, pauseMax));
}

function clickBusinessCards() {
  console.log('Starting clickBusinessCards...');

  const elem = page.$("//span[text()='Business Cards']");

  group("Click Business Cards", () => {
    elem.click();

    // Wait for next page to load
    // page.waitForLoadState('networkidle');

    // Wait for a specific element to appear
    page.waitForSelector("//a[text()='Browse designs']");
  });

  sleep(randomIntBetween(pauseMin, pauseMax));
}

function clickBrowseDesigns() {
  console.log('Starting clickBrowseDesigns...');

  const elem = page.$("//a[text()='Browse designs']");

  group("Click Browse Designs", () => {
    elem.click();

    // page.waitForLoadState('networkidle');

    page.waitForSelector("li.design-tile");
  });

  sleep(randomIntBetween(pauseMin, pauseMax));
}

function clickDesign(designName) {
  console.log('Starting clickDesign...');

  // fetch all of the design options
  const designOptions = page.$("li.design-tile");

  // ... incomplete script ;-)
}

Uncommenting page.waitForLoadState('networkidle') allows the script to proceed (although it eventually fails with panic: send on closed channel as per #49).

The only exception I'm expecting here is a timeout in case the selector was unsuccessful.

I think what might be happening is that the auto-wait on elem.click(); is not waiting long enough for the page to even change (the site is very slow at switching), and so we attempt to page.waitForSelector("li.design-tile") on a page/element that's about to be "detached".

robingustafsson commented 2 years ago

@tom-miseur I have a fix for a bug in waitForSelector. I discovered and fixed this issue when recording the obscon demo :)

The fix is basically changing https://github.com/grafana/xk6-browser/blob/main/common/element_handle.go#L886 to say rt.ToValue(opts.Timeout.Milliseconds()),, I have a PR with test case that I'll get around to pushing tomorrow hopefully 🙂 The effect of the current code is no waiting for the element to even attach (if not attached).

The page.waitForLoadState function doesn't actually work reliably as it'd need to be async to do so, as the time window between the click and the call to waitForLoadState could actually see the specified event fire and thus when waitForLoadState is called it waits forever, or rather until timeout. So in Playwright you'd first call waitForLoadState, get a promise back, call the action (click in this case) and then await the promise.