jest + puppetterでE2E自動テスト -- JavaScript無効、Cookie無効のテスト

JavaScriptブラウザテスト

全体

[https://github.com/wand2016/e2etestautomation_puppeteer:embed:cite]

index.htmlのソース、仕様

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <noscript>
      <meta http-equiv="refresh" content="0; URL=no-script.html" />
    </noscript>
    <title>
      index.html
    </title>
    <script>
     if (!navigator.cookieEnabled) {
         location.replace('/no-cookie.html');
     }
    </script>
  </head>
  <body>
    index.html
  </body>
</html>
  • JS無効時、/no-script.htmlにリダイレクト
  • Cookie無効時、/no-cookie.htmlに遷移

JavaScript無効時のテスト

test('JS無効だとno-script.htmlにリダイレクト', async () => {
    await page.setJavaScriptEnabled(false);
    await page.goto('http://localhost:8080/index.html', { timeout: 1000, waitUntil: 'domcontentloaded' });
    // DOMContentLoaded後、metaタグによるリダイレクト待ち
    await page.waitForNavigation({ timeout: 1000 });

    await expect(page.title()).resolves.toMatch('no-script.html');
});
  • これだけ: page.setJavaScriptEnabled(false);
  • Cookie無効判定にJavaScriptを使用するので、JavaScript無効のケースの考慮も必要

Cookie無効時のテスト

test('Cookie無効だとno-cookie.htmlにリダイレクト', async () => {

    await page.evaluateOnNewDocument(() => {
        Object.defineProperty(
            navigator,
            'cookieEnabled',
            { value: false }
        );
    });

    await Promise.all([
        page.goto('http://localhost:8080/index.html', { timeout: 1000, waitUntil: 'domcontentloaded' }),
        // 同期スクリプト実行・遷移待ち
        page.waitForNavigation({ timeout: 1000 })
    ]);

    await expect(page.title()).resolves.toMatch('no-cookie.html');
});

index.html (抜粋)

if (!navigator.cookieEnabled) {
    location.replace('/no-cookie.html');
}
  • navigator.cookieEnabledを直接参照してCookie無効判定している、テスタビリティを全く考慮していないやばそうなコード(実在)
  • puppeteer自体にCookie無効をemulateする機能はない
  • が、ドキュメント生成時にJSを実行することができ、そこでモック的な処理をさしはさむことができる
await page.evaluateOnNewDocument(() => {
    Object.defineProperty(
        navigator,
        'cookieEnabled',
        { value: false }
    );
});
  • navigator.cookieEnabledは「プロパティ

    • 通常のメンバ変数のように直接代入等をすることはできない

on chromium browser:

console.log(navigator.cookieEnabled);  // true

navigator.cookieEnabled = false;
console.log(navigator.cookieEnabled);  // still true
Object.getOwnPropertyDescriptor(navigator.__proto__, 'cookieEnabled')
{set: undefined, enumerable: true, configurable: true, get: }
  • ので、Object.definePropertyで上書き可能
Object.defineProperty(navigator, 'cookieEnabled', { value: false });
console.log(navigator.cookieEnabled);  // false
  • たまたまconfigurableだから助かったのであって、本当はnavigator.cookieEnabledを直接参照すること自体良くないんですけど

awaitとPromiseの意識

  • 雰囲気でawaitを書かない

    • 裏のPromiseを意識する
  • 並行して待たなければならないものはPromise.all()で包む

JavaScirpt無効判定

test('JS無効だとno-script.htmlにリダイレクト', async () => {
    await page.setJavaScriptEnabled(false);
    await page.goto('http://localhost:8080/index.html', { timeout: 1000, waitUntil: 'domcontentloaded' });
    // DOMContentLoaded後、metaタグによるリダイレクト待ち
    await page.waitForNavigation({ timeout: 1000 });

    await expect(page.title()).resolves.toMatch('no-script.html');
});
  • JavaScript無効時のリダイレクトの実装は<meta>タグによるHTTPリダイレクト
<noscript>
  <meta http-equiv="refresh" content="0; URL=no-script.html" />
</noscript>
  • これはDOMContentLoaded後に評価され実行されるので、まずpage.gotoをdomcontentloadedまで待ち終える:
await page.goto('http://localhost:8080/index.html', { timeout: 1000, waitUntil: 'domcontentloaded' });
  • 続いてHTTPリダイレクトのナビゲーションを待つ:
await page.waitForNavigation({ timeout: 1000 });

Cookie無効判定

  • Cookie無効判定の実装としては、<head>タグの中でJavaScriptを同期的に実行している

    • すなわち、ページ遷移はDOMContentLoaded前に発生する
    <script>
     if (!navigator.cookieEnabled) {
         location.replace('/no-cookie.html');
     }
    </script>
  </head>
  • ので、ページのDOMContentLoaded待ちと並行してページ遷移を待つ:
await Promise.all([
    page.goto('http://localhost:8080/index.html', { timeout: 1000, waitUntil: 'domcontentloaded' }),
    // 同期スクリプト実行・遷移待ち
    page.waitForNavigation({ timeout: 1000 })
]);