Lambda(node18)でplaywrightを動作させるコードをtypescriptで書いたメモ

## 概要
aws lambdaでPlaywrightを動作させた。
ローカルで動作させる場合と異なり、Lambdaにはブラウザがない。
ヘッドレスブラウザを含んだLayerを利用することにした。

[ソースコード](https://github.com/hibohiboo/aws-cdk-v2/tree/1ac55c10ca4bacc90088dba3dacf7fc92c8ad343)

## ソース

```ts:cdk/lib/playwright-stack.ts
import { Aspects, Duration, Stack, StackProps, Tag } from 'aws-cdk-lib';
import { LayerVersion, Runtime } from 'aws-cdk-lib/aws-lambda';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { Construct } from 'constructs';

export class PlayWrightStack extends Stack {
  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);
    new NodejsFunction(this, 'playWrightLambda', {
      runtime: Runtime.NODEJS_18_X,
      memorySize: 1024,
      timeout: Duration.seconds(10),
      entry: `../src/handler/invoke/playwright.ts`,
      bundling: { externalModules: ['@sparticuz/chromium'] },
      layers: [
        LayerVersion.fromLayerVersionArn(
          this,
          'chromium-lambda-layer',
          'arn:aws:lambda:ap-northeast-1:764866452798:layer:chrome-aws-lambda:39',
        ),
      ],
    });

    Aspects.of(this).add(new Tag('Stack', 'PlayWrightStack'));
  }
}
```

```json:packege.json
{
  "name": "aws-cdk-v2",
  "dependencies": {
    "playwright-core": "^1.39.0"
  },
  "devDependencies": {
    "@sparticuz/chromium": "^118.0.0",
    "@types/aws-lambda": "^8.10.126",
    "typescript": "^5.2.2"
  }
}

```
```ts:src/handler/invoke/playwright.ts
import { Handler } from 'aws-lambda';
import { chromium } from 'playwright-core';

const sparticuzChromium = require('@sparticuz/chromium');

sparticuzChromium.setHeadlessMode = true;
sparticuzChromium.setGraphicsMode = false;

export const handler: Handler = async (event, context) => {
  let browser;
  try {
    const executablePath = await sparticuzChromium.executablePath();
    browser = await chromium.launch({
      args: sparticuzChromium.args,
      executablePath,
      headless: sparticuzChromium.headless,
    });

    const context = await browser.newContext();
    const page = await context.newPage();
    await page.goto('https://talto.cc/');
    const locators = await page.locator('section ul li').all();
    const promises = locators.map(async (locator) => {
      const elment = await locator.elementHandle();
      const imgElement = await elment?.$('img');
      if (!imgElement) return;
      const alt = await imgElement.getAttribute('alt');
      if (!alt) return;
      console.log(alt);
    });
    await Promise.all(promises);
  } catch (e) {
    console.error(e);
    throw e;
  } finally {
    if (browser) await browser.close();
  }
};
```

## デプロイ
レイヤーが設定されていることを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/79986db5-aa26-6a68-c57b-ca38ec96f47c.png)

## テスト実行
コンソールからテスト実行を行った。
今回のplaywrightは https://talto.cc/ のトップの新着シナリオの名前を取得するコードとなっている。
Lambdaのログに下記が出力されており、成功したことが確認できた。

```
START RequestId: 063e5b24-735e-45d6-a7aa-5ad74648879f Version: $LATEST
2023-11-08T13:45:58.005Z	063e5b24-735e-45d6-a7aa-5ad74648879f	INFO	第二次遍歴遠征異聞：開かれたる館
2023-11-08T13:45:58.020Z	063e5b24-735e-45d6-a7aa-5ad74648879f	INFO	邂逅する真像
2023-11-08T13:45:58.021Z	063e5b24-735e-45d6-a7aa-5ad74648879f	INFO	怪談白物語シナリオ　『カッパの手』
2023-11-08T13:45:58.022Z	063e5b24-735e-45d6-a7aa-5ad74648879f	INFO	怪談白物語シナリオ　『アパートの壁』
2023-11-08T13:45:58.022Z	063e5b24-735e-45d6-a7aa-5ad74648879f	INFO	スレイヤー・オブ・ドラゴンスレイヤー　或いは少女リサと雨降りな日々
2023-11-08T13:45:58.024Z	063e5b24-735e-45d6-a7aa-5ad74648879f	INFO	『キャスリング　☆　すぴりっと』
2023-11-08T13:45:58.024Z	063e5b24-735e-45d6-a7aa-5ad74648879f	INFO	マシュマロプラネット
2023-11-08T13:45:58.024Z	063e5b24-735e-45d6-a7aa-5ad74648879f	INFO	白雷  -ハクライ-
2023-11-08T13:45:58.025Z	063e5b24-735e-45d6-a7aa-5ad74648879f	INFO	新約・きさらぎ駅
2023-11-08T13:45:58.025Z	063e5b24-735e-45d6-a7aa-5ad74648879f	INFO	おっと！！そっちは二次創作のKPCだ
2023-11-08T13:45:58.042Z	063e5b24-735e-45d6-a7aa-5ad74648879f	INFO	客演裁判　スクランブる逆転
2023-11-08T13:45:58.043Z	063e5b24-735e-45d6-a7aa-5ad74648879f	INFO	Twins
2023-11-08T13:45:58.043Z	063e5b24-735e-45d6-a7aa-5ad74648879f	INFO	banner
2023-11-08T13:45:58.044Z	063e5b24-735e-45d6-a7aa-5ad74648879f	INFO	banner
END RequestId: 063e5b24-735e-45d6-a7aa-5ad74648879f
REPORT RequestId: 063e5b24-735e-45d6-a7aa-5ad74648879f	Duration: 9592.31 ms	Billed Duration: 9593 ms	Memory Size: 1024 MB	Max Memory Used: 769 MB	Init Duration: 450.23 ms	
```

## 設定ミスで起きたエラーのメモ

### chromiumがない

```
ERROR	[Error: Executable doesn't exist at /home/sbx_user1051/.cache/ms-playwright/chromium-1084/chrome-linux/chrome
╔═════════════════════════════════════════════════════════════════════════╗
║ Looks like Playwright Test or Playwright was just installed or updated. ║
║ Please run the following command to download new browsers:              ║
║                                                                         ║
║     npx playwright install                                              ║
║                                                                         ║
║ <3 Playwright Team                                                      ║
╚═════════════════════════════════════════════════════════════════════════╝]
```

```
ERROR	[Error: ENOENT: no such file or directory, open '/var/task/bin/chromium.br'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: '/var/task/bin/chromium.br'
}
```

```
ERROR	[Error: spawn ETXTBSY
=========================== logs ===========================
<launching> /tmp/chromium --disable-field-trial-config --disable-background-networking --enable-features=NetworkService,NetworkServiceInProcess --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-back-forward-cache --disable-breakpad --disable-client-side-phishing-detection --disable-component-extensions-with-background-pages --disable-component-update --no-default-browser-check --disable-default-apps --disable-dev-shm-usage --disable-extensions --disable-features=ImprovedCookieControls,LazyFrameLoading,GlobalMediaControls,DestroyProfileOnBrowserClose,MediaRouter,DialMediaRouteProvider,AcceptCHFrame,AutoExpandDetailsElement,CertificateTransparencyComponentUpdater,AvoidUnnecessaryBeforeUnloadCheckSync,Translate --allow-pre-commit-input --disable-hang-monitor --disable-ipc-flooding-protection --disable-popup-blocking --disable-prompt-on-repost --disable-renderer-backgrounding --force-color-profile=srgb --metrics-recording-only --no-first-run --enable-automation --password-store=basic --use-mock-keychain --no-service-autorun --export-tagged-pdf --disable-search-engine-choice-screen --headless --hide-scrollbars --mute-audio --blink-settings=primaryHoverType=2,availableHoverTypes=2,primaryPointerType=4,availablePointerTypes=4 --no-sandbox --autoplay-policy=user-gesture-required --disable-background-networking --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-breakpad --disable-client-side-phishing-detection --disable-component-update --disable-default-apps --disable-dev-shm-usage --disable-domain-reliability --disable-extensions --disable-features=AudioServiceOutOfProcess --disable-hang-monitor --disable-ipc-flooding-protection --disable-notifications --disable-offer-store-unmasked-wallet-cards --disable-popup-blocking --disable-print-preview --disable-prompt-on-repost --disable-renderer-backgrounding --disable-setuid-sandbox --disable-speech-api --disable-sync --disk-cache-size=33554432 --hide-scrollbars --ignore-gpu-blacklist --metrics-recording-only --mute-audio --no-default-browser-check --no-first-run --no-pings --no-sandbox --no-zygote --password-store=basic --use-gl=swiftshader --use-mock-keychain --single-process --user-data-dir=/tmp/playwright_chromiumdev_profile-YJz7Hd --remote-debugging-pipe --no-startup-window
============================================================]
============================================================]
```

```
ERROR	Error: The input directory "/var/bin" does not exist.
    at Chromium.executablePath (/var/task/index.js:68049:17)
    at Runtime.handler (/var/task/index.js:68135:52)
    at Runtime.handleOnceNonStreaming (file:///var/runtime/index.mjs:1173:29)
```

### ヘッドレスブラウザの設定不足

```
 ERROR [ValidationError: headless: expected boolean, got string]
```


## 参考
[@sparticuz/chromium](https://github.com/Sparticuz/chromium)
[chrome-aws-lambda-layer](https://github.com/shelfio/chrome-aws-lambda-layer)
[playwright](https://playwright.dev/docs/intro)
[これだけはおさえておきたいPlaywrightコマンド集](https://qiita.com/oh_rusty_nail/items/d955e3273994214a0afa)
[2020年: PuppeteerとPlaywrightの激動の１年。](https://qiita.com/YusukeIwaki/items/f559c11d08df1a2df465)