2

I was trying to make a fun project by automating a typing test using puppeteer, but when I want to press space this error pops up

D:\scraping\puppeteer tut\node_modules\puppeteer\lib\cjs\puppeteer\common\assert.js:28
        throw new Error(message);
              ^

Error: Unknown key: " "
    at assert (D:\scraping\puppeteer tut\node_modules\puppeteer\lib\cjs\puppeteer\common\assert.js:28:15)
    at Keyboard._Keyboard_keyDescriptionForString (D:\scraping\puppeteer tut\node_modules\puppeteer\lib\cjs\puppeteer\common\Input.js:265:28) 
    at Keyboard.down (D:\scraping\puppeteer tut\node_modules\puppeteer\lib\cjs\puppeteer\common\Input.js:112:119)
    at Keyboard.press (D:\scraping\puppeteer tut\node_modules\puppeteer\lib\cjs\puppeteer\common\Input.js:231:20)
    at D:\scraping\puppeteer tut\typingTest.js:37:34
    at processTicksAndRejections (node:internal/process/task_queues:96:5)

The code for the project is

const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: false
  });
  const page = await browser.newPage();
  await page.goto("https://www.speedtypingonline.com/typing-test", {
    waitUntil: "load"
  });
  const word = await page.evaluate(() => {
    let lineDivArr = document.querySelectorAll(".blockLines");
    let charArr = [];
    let ActualChar = [];
    lineDivArr.forEach((line) => {
      charArr.push(line.querySelectorAll('span'));
    })

    charArr.forEach((char) => {
      char.forEach((c) => {
        ActualChar.push(c.outerText);
      })
    })
    return ActualChar;
  })
  for (const element of word) {
    if (element == String.fromCharCode(32)) {
      await page.keyboard.press(String.fromCharCode(32));
    } else await page.keyboard.press(element);
  }
})();

In addition to the above error, I occasionally encounter this error mentioned here:

Error: We either navigate top level or have old version of the navigated frame

Kindly help me resolve the issue.

ggorlen
  • 44,755
  • 7
  • 76
  • 106

1 Answers1

0

I'm happy to see a site that consistently reproduces the "We either navigate top level or have old version of the navigated frame" error. I'm not sure I've 100% solved it, but when I navigate to the page without ad blockers, there appear to be a few reloads that seem triggered by iframes/Google (ads|analytics) junk. After blocking those requests and deleting the iframes, things are reliable enough for a toy script.

Next, the space character issue is caused by the site using rich text spaces (and other non-ASCII characters). They look like ASCII but aren't (debug with letter.charCodeAt() to see that they're out of range). The solution is to detect these characters and fire the ASCII equivalent. I only did it for the character code 160 space, which is enough to get 99-100% accuracy on most tests.

Here's my code so far. Other known issues:

  • Occasional assertion failure probably related to the "We navigate..." error, but without any message (I suspect my iframe removal code has a race condition). I didn't look into it deeply.
  • Technically, there's a race condition on the .nxtLetter element but in practice I think it's virtually impossible for the browser to hang long enough that Puppeteer will manage to get two keystrokes in before the class moves to another element. Due to the complexity of the task, it's possible that things can get out of sync and fail--my goal was to get 95% in the ballpark rather than write a perfect bot.
  • Selecting and typing one character at a time has a lot of process overhead. I originally tried typing the whole text at once with page.type() but I ran into mistakes too easily, so I erred on the side of reliability. Ditto for checking the stopping condition; lots of wasted cycles here. I'll leave it as an exercise to speed things up.
const puppeteer = require("puppeteer"); // ^19.0.0

let browser;
(async () => {
  browser = await puppeteer.launch({headless: false});
  const [page] = await browser.pages();
  await page.setRequestInterception(true);
  page.on("request", request => {
    if (/google|cloudflare/.test(request.url())) {
      request.abort();
    }
    else {
      request.continue();
    }
  });
  await page.goto("https://www.speedtypingonline.com/typing-test", {
    waitUntil: "domcontentloaded"
  });
  await page.evaluate(`
    [...document.querySelectorAll("iframe")]
      .forEach(e => e.remove());
  `);
  const letterSel = ".nxtLetter"
  await page.waitForSelector(letterSel);

  while (!(await page.$("#WPM_Result span"))) {
    const letter = await page.$$eval(letterSel, els => {
      const letter = els.at(-1).textContent;
      return letter.charCodeAt() === 160 ? " " : letter;
    });
    await page.type(letterSel, letter, {delay: 0});
  }

  await page.screenshot({
    path: "typing-results.png",
    fullPage: true
  });
})()
  .catch(err => console.error(err))
  .finally(() => browser?.close())
;
ggorlen
  • 44,755
  • 7
  • 76
  • 106