Ошибка драматурга (цель закрыта) после навигации

Я пробую что-то очень простое:

  • Перейдите на google.com
  • Заполните поле поиска словом "сыр"
  • Нажмите Enter в поле поиска
  • Выведите текст заголовка первого результата

Так просто, но я не могу заставить его работать. Это код:

const playwright = require('playwright');

(async () => {
  for (const browserType of ['chromium', 'firefox', 'webkit']) {
    const browser = await playwright[browserType].launch();
    try {
      const context = await browser.newContext();
      const page = await context.newPage();
      await page.goto('https://google.com');
      await page.fill('input[name=q]', 'cheese');
      await page.press('input[name=q]', 'Enter');
      await page.waitForNavigation();

      page.waitForSelector('div#rso h3')
          .then(firstResult => console.log(`${browserType}: ${firstResult.textContent()}`))
          .catch(error => console.error(`Waiting for result: ${error}`));
    } catch(error) {
      console.error(`Trying to run test on ${browserType}: ${error}`);
    } finally {
      await browser.close();
    }
  }
})();

Сначала я пытался получить первый результат с помощью page.$()но это не сработало. Немного изучив проблему, я обнаружил, чтоpage.waitForNavigation() я думал, что это решение, но это не так.

Я использую последнюю версию драматурга: 1.0.2.

4 ответа

Мне кажется, единственная проблема заключалась в том, что вы оценили textContentнеправильно. Реорганизовавpage.waitForSelector обещать async / await и использовать page.$eval получить textContentон работает отлично, больше нет целевых закрытых ошибок (например, вы случайно захотели использоватьwaitForSelector оценить ElementHandle?).

try {
      const context = await browser.newContext();
      const page = await context.newPage();
      await page.goto('https://google.com');
      await page.fill('input[name=q]', 'cheese');
      await page.press('input[name=q]', 'Enter');
      await page.waitForNavigation();

      // page.waitForSelector('div#rso h3').then(firstResult => console.log(`${browserType}: ${firstResult.textContent()}`)).catch(error => console.error(`Waiting for result: ${error}`));

      await page.waitForSelector('div#rso h3');
      const firstResult = await page.$eval('div#rso h3', firstRes => firstRes.textContent);
      console.log(`${browserType}: ${firstResult}`)
    } catch(error) {
      console.error(`Trying to run test on ${browserType}: ${error}`);
    } finally {
      await browser.close();
    }
  }

Выход:

chrome: Cheese – Wikipedia
firefox: Cheese – Wikipedia
webkit: Cheese – Wikipedia

Примечание: Chrome и webkit работают, firefox не работает.waitForNavigationдля меня. Если бы я заменил его наawait page.waitForTimeout(5000);firefox тоже работал. Это может быть проблема с поддержкой Firefox драматургом для обещания навигации.

  1. В моем случае ошибка драматурга Target closedпоявляется при первой попытке получить текст со страницы. Ошибка неверна, фактическая причина заключалась в том, что на целевом сайте была включена базовая аутентификация. Драматург не смог открыть страницу и просто застрял с надписью «Цель закрыта».

            const options = { 
       httpCredentials = { username: 'user', password: 'password'}
    };
    const context = await browser.newContext(options);
    
  2. Еще одна проблема заключалась в том, что локальные тесты запускались без проблем, включая контейнеры Docker, в то время как Github CI терпел неудачу с Playwright без каких-либо подробностей, кроме приведенной выше ошибки.
    Причина была в специальном символе в Github Secret. Например, знак доллара будет просто удален из секрета в Github Actions. Чтобы исправить это, используйте либо env:раздел

            env:
      XXX: ${ secrets.SUPER_SECRET }
    

    или заключить секрет в одинарные кавычки:

            run: |
      export XXX='${{ secrets.YYY}}'
    

    Аналогичная специфика экранирования существует в Kubernetes, Docker и Gitlub; $$становится $а также z$abcстановится z.

  3. Использовать mcr.microsoft.com/playwrightобраз docker hub от Microsoft с предустановленными node, npm и playwright. В качестве альтернативы во время установки playwright не забудьте установить зависимости системных пакетов, запустив npx playwright install-deps.

  4. Виртуальная машина должна иметь достаточно ресурсов для обработки тестов браузера. Распространенная проблема в рабочих процессах CI/CD.

Если вы ждете page.press('input[name=q]', 'Enter'); это может быть слишком поздно для waitForNavigation работать.

Вы можете удалить awaitна звонке для прессы. Вам может потребоваться подождать навигации, а не действия нажатия.

const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://google.com');
await page.fill('input[name=q]', 'cheese');
page.press('input[name=q]', 'Enter');
await page.waitForNavigation();

var firstResult = await page.waitForSelector('div#rso h3');
console.log(`${browserType}: ${await firstResult.textContent()}`);

Также обратите внимание, что вам нужно await за textContent().

Обязательно выполняйте все обещания и избегайте объединения thenс await.

      //vvv
await page.waitForSelector('div#rso h3')
//^^^

Обратите внимание, чтоawait page.waitForNavigation();может вызвать состояние гонки, если вызывается после события, которое запускает навигацию. Обычно я избегаю ожидания селектора или условия, которое появится на следующей странице. Обычно это приводит к более быстрому, короткому и надежному коду.

Если вы используетеwaitForNavigation, установите его рядом сPromise.allили до события триггера навигации, в этом случаеpress.


После этих корректировок, если ваша цель состоит в том, чтобы получить данные как можно быстрее и надежнее, а не тестировать шаги на этом пути, есть возможности для улучшения.

Часто нет необходимости переходить на целевую страницу, а затем вводить текст в поле, чтобы запустить поиск. Обычно быстрее и менее подвержено ошибкам перейти непосредственно на страницу результатов с запросом, закодированным в URL-адресе. В этом случае ваш код можно сократить до

      const url = "https://www.google.com/search?q=cheese";
await page.goto(url, {waitUntil: "networkidle"});
console.log(await page.textContent(".fP1Qef h3"));

Если вы заметили, что нужный вам текст находится в статическом HTML, как в данном случае, вы можете пойти дальше и заблокировать JS и внешние ресурсы:

      const playwright = require("playwright"); // ^1.30.1

let browser;
let context;
(async () => {
  browser = await playwright.chromium.launch();
  context = await browser.newContext({javaScriptEnabled: false});
  const page = await context.newPage();
  const url = "https://www.google.com/search?q=cheese";
  await page.route("**", route => {
    if (route.request().url().startsWith(url)) {
      route.continue();
    }
    else {
      route.abort();
    }
  });

  // networkidle is a suboptimal way to handle redirection
  await page.goto(url, {waitUntil: "networkidle"});
  console.log(await page.locator(".fP1Qef h3").allTextContents());
})()
  .catch(err => console.error(err))
  .finally(async () => {
    await context?.close();
    await browser?.close();
  });

Заблокировав JS и все внешние ресурсы, вы часто можете перейти к Святому Граалю парсинга веб-страниц: полностью отказаться от автоматизации браузера и вместо этого использовать HTTP-запрос и облегченный парсер HTML:

      const cheerio = require("cheerio"); // 1.0.0-rc.12

const query = "cheese";
const url = `https://www.google.com/search?q=${encodeURIComponent(query)}`;

fetch(url, { // Node 18 or install node-fetch
  headers: {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
  }
})
  .then(res => res.text())
  .then(html => {
    const $ = cheerio.load(html);
    console.log($(".LC20lb").first().text()); // first result
    console.log([...$(".LC20lb")].map(e => $(e).text())); // all results
  });
Другие вопросы по тегам