playwrightでAPIへリクエスト送信
はじめに
APIを使う事例が少なく、実行後の確認にSlackへ送信するコードを書いておく必要がありました。
環境
Slack
Incomming Webhookを有効化したSlackアプリを利用しています。
無料版でもアプリは10個は作れるようなので、検証や履歴を気にしないログに使えます。
Incoming WebhooksをON
コード
import { test ,request} from '@playwright/test';
const SLACK_BASE_URL = "https://hooks.slack.com/"
const SLACK_INCOMING_WEBHOOK_PATH = "services/..."
test.describe("まとめて実行", () => {
test('SlackのAPIへ通知するパターン1', async () => {
const context = await request.newContext();
await context.post(SLACK_BASE_URL + SLACK_INCOMING_WEBHOOK_PATH,{
headers: {
Accept : 'application/json',
},
data: {
text : 'パターン1'
}
});
});
test('SlackのAPIへ通知するパターン2', async () => {
const context = await request.newContext({
baseURL: SLACK_BASE_URL,
});
await context.post(SLACK_INCOMING_WEBHOOK_PATH, {
headers: {
Accept : 'application/json',
},
data: {
text : 'パターン2'
}
});
});
})
さいごに
例を見ると認証情報を保持したり、二次利用できたり便利になっていました。
参考
GitHub Actionsを使う
.github/workflows にplaywright.ymlを設定して
他のソースと一緒にGitHubに追加
name: Playwright Tests
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 2
- run: git checkout HEAD^
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 1
テスト結果
artifactとして取得
課金は?
有料版を使ってるので50時間くらいはだいじょうぶか
.envファイルを使う時
理不尽なエラーに困る
型 'string | undefined' の引数を型 'string' のパラメーターに割り当てることはできません。
//環境変数に値がなければ.envファイルをよむ、あれば読み込まない
if (typeof process.env.SLACK_INCOMING_WEBHOOK_PATH == 'undefined') {
require('dotenv').config();
}
//process.envはundefinedの型も含むのでstringにしておく
const SLACK_INCOMING_WEBHOOK_PATH :string = (process.env.SLACK_INCOMING_WEBHOOK_PATH as string);
参考
シナリオ
- ファイルをダウンロード
- Shift_jisからUTF8へ変換
コード生成
$ npx playwright codegen https://www.toukei.metro.tokyo.lg.jp/jsuikei/js-index.htm
こちらのコードは今は動くが、今後動かないこともある。
test.describe("ファイルをダウンロード", () => {
test('test', async ({ page }) => {
// Go to https://www.toukei.metro.tokyo.lg.jp/jsuikei/js-index.htm
await page.goto('https://www.toukei.metro.tokyo.lg.jp/jsuikei/js-index.htm');
// Click text=CSV_1(7KB) >> nth=0
const [download1] = await Promise.all([
page.waitForEvent('download'),
page.locator('text=CSV_1(7KB)').first().click()
]);
const st = await download1.createReadStream()
if (st){
await st
.pipe(iconv.decodeStream('SHIFT_JIS'))
.pipe(iconv.encodeStream('utf8'))
.pipe(fs.createWriteStream('./data/data_utf8.csv'));
}
});
})
参考
シナリオ
- ファイルをダウンロード
- Shift_jisからUTF8へ変換
- 内容を精査
コード
import {createInterface} from 'node:readline';
import {once} from 'node:events'
import iconv from 'iconv-lite'
import {test ,request} from '@playwright/test';
const SLACK_BASE_URL = "https://hooks.slack.com/"
test.describe("ファイルをダウンロード", () => {
test('test', async ({ page }) => {
// Go to https://www.toukei.metro.tokyo.lg.jp/jsuikei/js-index.htm
await page.goto('https://www.toukei.metro.tokyo.lg.jp/jsuikei/js-index.htm');
// Click text=CSV_1(7KB) >> nth=0
const [download1] = await Promise.all([
page.waitForEvent('download'),
page.locator('text=CSV_1(7KB)').first().click()
]);
const st = await download1.createReadStream()
if (st){
const readline = createInterface(
await st
.pipe(iconv.decodeStream('SHIFT_JIS'))
// .pipe(iconv.encodeStream('utf8'))
)
readline.on('line', (line: string) => {
// Process the line.
console.log(line.split(',')[0])
});
await once(readline, 'close');
console.log('File processed.');
}
});
})
気になったところ
- CJSとESM
importの書き方を揃えるところから。あんまり分かってない。
参考
シナリオ
- ファイルをダウンロード
- Shift_jisからUTF8へ変換
- 内容を精査
- Slackへ通知
import {createInterface} from 'node:readline';
import {once} from 'node:events'
import iconv from 'iconv-lite'
import {test ,request} from '@playwright/test';
const SLACK_BASE_URL = "https://hooks.slack.com/"
test.describe("ファイルをダウンロード", () => {
test('test', async ({ page }) => {
// Go to https://www.toukei.metro.tokyo.lg.jp/jsuikei/js-index.htm
await page.goto('https://www.toukei.metro.tokyo.lg.jp/jsuikei/js-index.htm');
// Click text=CSV_1(7KB) >> nth=0
const [download1] = await Promise.all([
page.waitForEvent('download'),
page.locator('text=CSV_1(7KB)').first().click()
]);
const st = await download1.createReadStream()
if (st){
const readline = createInterface(
await st
.pipe(iconv.decodeStream('SHIFT_JIS'))
// .pipe(iconv.encodeStream('utf8'))
)
readline.on('line', (line: string) => {
// Process the line.
let arr = line.split(',')
if (arr[3] == '足立区'){
sendSlack(arr[3])
}
});
await once(readline, 'close');
console.log('File processed.');
}
});
})
//環境変数に値がなければ.envファイルをよむ、あれば読み込まない
if (typeof process.env.SLACK_INCOMING_WEBHOOK_PATH == 'undefined') {
require('dotenv').config();
}
async function sendSlack(str: string) {
const context = await request.newContext({
baseURL: SLACK_BASE_URL,
});
await context.post(SLACK_INCOMING_WEBHOOK_PATH, {
headers: {
Accept : 'application/json',
},
data: {
text : `${str}`
}
});
}
不具合対応
具体的にはファイルから読み込んだファイルを一行ずつ処理しているタイミングで
Slackへ通知をすると同期周りがうまくいかず。
取得したデータは配列に持って、まとめてSlackへ通知するように修正。
import {createInterface} from 'node:readline';
import {once} from 'node:events'
import iconv from 'iconv-lite'
import {test ,request, type Page, expect} from '@playwright/test';
const SLACK_BASE_URL = "https://hooks.slack.com/"
let apiContext;
test.beforeAll(async ({ playwright }) => {
apiContext = await playwright.request.newContext({
// All requests we send go to this API endpoint.
baseURL: SLACK_BASE_URL
});
})
test.afterAll(async ({ }) => {
// Dispose all responses.
await apiContext.dispose();
});
test.describe("ファイルをダウンロード", () => {
test('ダウンロードチェック', async ({ page }) => {
// Go to https://www.toukei.metro.tokyo.lg.jp/jsuikei/js-index.htm
await page.goto('https://www.toukei.metro.tokyo.lg.jp/jsuikei/js-index.htm');
// Click text=CSV_1(7KB) >> nth=0
const [download1] = await Promise.all([
page.waitForEvent('download'),
page.locator('text=CSV_1(7KB)').first().click()
]);
let senddata:Array<string> = new Array()
const st = await download1.createReadStream()
if (st){
const readline = createInterface(
await st
.pipe(iconv.decodeStream('SHIFT_JIS'))
// .pipe(iconv.encodeStream('utf8'))
)
readline.on('line',async (line:string) => {
// Process the line.
let arr = line.split(',')
if (arr[3] === '足立区' || arr[3] === '荒川区'){
senddata.push(`${arr[0]}番目${arr[3]}`)
}
});
await once(readline, 'close');
console.log('File processed.');
// for await (const line of readline) {
// // Each line in input.txt will be successively available here as `line`.
// // console.log(`Line from file: ${line}`);
// let arr = line.split(',')
// if (arr[3] === '足立区'){
// senddata.push(`${arr[0]}番目${arr[3]}`)
// }
// }
await sendSlack(senddata.join("\r\n"))
}
});
})
//環境変数に値がなければ.envファイルをよむ、あれば読み込まない
if (typeof process.env.SLACK_INCOMING_WEBHOOK_PATH == 'undefined') {
require('dotenv').config();
}
//process.envはundifineの型も含むのでstringにしておく
const SLACK_INCOMING_WEBHOOK_PATH :string = (process.env.SLACK_INCOMING_WEBHOOK_PATH as string);
// test.describe("まとめて実行", () => {
// test('SlackのAPIへ通知するパターン1', async () => {
// const context = await request.newContext();
// await context.post(SLACK_BASE_URL + SLACK_INCOMING_WEBHOOK_PATH,{
// headers: {
// Accept : 'application/json',
// },
// data: {
// text : 'パターン1'
// }
// });
// });
// test('SlackのAPIへ通知するパターン2', async () => {
// const context = await request.newContext({
// baseURL: SLACK_BASE_URL,
// });
// await context.post(SLACK_INCOMING_WEBHOOK_PATH, {
// headers: {
// Accept : 'application/json',
// },
// data: {
// text : 'パターン2'
// }
// });
// });
// })
async function sendSlack(str: string) {
const res = await apiContext.post(SLACK_INCOMING_WEBHOOK_PATH, {
headers: {
Accept : 'application/json',
},
data: {
text : str
}
});
expect(res.ok()).toBeTruthy();
}
参考
同期・非同期とファイル
ファイルの処理が終了してからSlackで通知するようにと
readline.on('line',async (line:string) => {
// Process the line.
let arr = line.split(',')
if (arr[3] === '足立区' || arr[3] === '荒川区'|| arr[3] === '千代田区'){
senddata.push(`${arr[0]}番目${arr[3]}`)
}
});
await once(readline, 'close');
// console.log('File processed.');
//ファイルの処理が終わってから実行する
await sendSlack(senddata.join("\r\n"))
コードを書いて後から理解するフローから抜けられないのね・・・
GitHub Actionsを使う - self-hosted
自前のraspberryPi(ubuntuのv22系)で実行させるように環境構築
- raspberryPiの環境そのまま
- docker
runner
- プロジェクト
- 組織単位(個人用では設定表示されない)
プロジェクト用で設定。
New self-hosted runner を 押す。
Runner image を Linux Architecture を ARM64
configure
期限切れになったら、同じページを再読み込みすることで新しいtokenを確認できます。
一度認証されれば再び使うことはなし。