diff --git a/CHANGES.md b/CHANGES.md index 7c6cb3aa..0c59a0f8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,8 @@ ### misc +- [ADD] E2E テストに `"type": "switched"` のテストを追加する + - @voluntas - [CHANGE] canary リリース方法を `canary.py` に変更する - `release_canary.sh` は削除 - @voluntas diff --git a/examples/sendonly/main.mts b/examples/sendonly/main.mts index e4d250a6..a4ca9a4e 100644 --- a/examples/sendonly/main.mts +++ b/examples/sendonly/main.mts @@ -1,5 +1,6 @@ import Sora, { type SignalingNotifyMessage, + type SignalingEvent, type ConnectionPublisher, type SoraConnection, } from 'sora-js-sdk' @@ -81,7 +82,10 @@ class SoraClient { this.metadata = { access_token: access_token } this.connection = this.sora.sendonly(this.channelId, this.metadata, this.options) - this.connection.on('notify', this.onnotify.bind(this)) + this.connection.on('notify', this.onNotify.bind(this)) + + // E2E テスト用のコード + this.connection.on('signaling', this.onSignaling.bind(this)) } async connect(stream: MediaStream): Promise { @@ -109,7 +113,7 @@ class SoraClient { return this.connection.pc.getStats() } - private onnotify(event: SignalingNotifyMessage): void { + private onNotify(event: SignalingNotifyMessage): void { if ( event.event_type === 'connection.created' && this.connection.connectionId === event.connection_id @@ -120,4 +124,11 @@ class SoraClient { } } } + + // E2E テスト用のコード + private onSignaling(event: SignalingEvent): void { + if (event.type === 'onmessage-switched') { + console.log('[signaling]', event.type, event.transportType) + } + } } diff --git a/package.json b/package.json index 421a4da8..90e233ea 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,9 @@ "url": "https://discord.gg/shiguredo" }, "homepage": "https://github.com/shiguredo/sora-js-sdk#readme", - "files": ["dist"], + "files": [ + "dist" + ], "devDependencies": { "@biomejs/biome": "1.9.4", "@playwright/test": "1.49.0", @@ -41,8 +43,8 @@ "vite": "6.0.1", "vitest": "2.1.6" }, - "packageManager": "pnpm@9.14.3", + "packageManager": "pnpm@9.15.0", "engines": { "node": ">=18" } -} +} \ No newline at end of file diff --git a/tests/switched.spec.ts b/tests/switched.spec.ts new file mode 100644 index 00000000..3a36986d --- /dev/null +++ b/tests/switched.spec.ts @@ -0,0 +1,88 @@ +import { expect, test } from '@playwright/test' + +test('sendonly type:switched pages', async ({ browser }) => { + // 新しいページを2つ作成 + const sendonly = await browser.newPage() + + sendonly.on('console', (msg) => { + console.log(msg.type(), msg.text()) + }) + + // それぞれのページに対して操作を行う + await sendonly.goto('http://localhost:9000/sendonly/') + + // SDK バージョンの表示 + await sendonly.waitForSelector('#sdk-version') + const sendonlySdkVersion = await sendonly.$eval('#sdk-version', (el) => el.textContent) + console.log(`sendonly sdkVersion=${sendonlySdkVersion}`) + + await sendonly.click('#connect') + // console.log に [signaling] switched が出力されるまで待機するための Promise を作成する + const consolePromise = sendonly.waitForEvent('console') + + // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ + await sendonly.waitForSelector('#connection-id:not(:empty)') + + // #sendonly-connection-id 要素の内容を取得 + const sendonlyConnectionId = await sendonly.$eval('#connection-id', (el) => el.textContent) + console.log(`sendonly connectionId=${sendonlyConnectionId}`) + + // レース対策 + await sendonly.waitForTimeout(3000) + + // Console log の Promise が解決されるまで待機する + const msg = await consolePromise + // log [signaling] switched websocket が出力されるので、args 0/1/2 をそれぞれチェックする + // [signaling] + const value1 = await msg.args()[0].jsonValue() + expect(value1).toBe('[signaling]') + // switched + const value2 = await msg.args()[1].jsonValue() + expect(value2).toBe('onmessage-switched') + // websocket + const value3 = await msg.args()[2].jsonValue() + expect(value3).toBe('websocket') + + // 'Get Stats' ボタンをクリックして統計情報を取得 + await sendonly.click('#get-stats') + + // 統計情報が表示されるまで待機 + await sendonly.waitForSelector('#stats-report') + // データセットから統計情報を取得 + const sendonlyStatsReportJson: Record[] = await sendonly.evaluate(() => { + const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement + return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] + }) + + // sendonly audio codec + const sendonlyAudioCodecStats = sendonlyStatsReportJson.find( + (report) => report.type === 'codec' && report.mimeType === 'audio/opus', + ) + expect(sendonlyAudioCodecStats).toBeDefined() + + // sendonly audio outbound-rtp + const sendonlyAudioOutboundRtp = sendonlyStatsReportJson.find( + (report) => report.type === 'outbound-rtp' && report.kind === 'audio', + ) + expect(sendonlyAudioOutboundRtp).toBeDefined() + expect(sendonlyAudioOutboundRtp?.bytesSent).toBeGreaterThan(0) + expect(sendonlyAudioOutboundRtp?.packetsSent).toBeGreaterThan(0) + + // sendonly video codec + const sendonlyVideoCodecStats = sendonlyStatsReportJson.find( + (stats) => stats.type === 'codec' && stats.mimeType === 'video/VP9', + ) + expect(sendonlyVideoCodecStats).toBeDefined() + + // sendonly video outbound-rtp + const sendonlyVideoOutboundRtpStats = sendonlyStatsReportJson.find( + (stats) => stats.type === 'outbound-rtp' && stats.kind === 'video', + ) + expect(sendonlyVideoOutboundRtpStats).toBeDefined() + expect(sendonlyVideoOutboundRtpStats?.bytesSent).toBeGreaterThan(0) + expect(sendonlyVideoOutboundRtpStats?.packetsSent).toBeGreaterThan(0) + + await sendonly.click('#disconnect') + + await sendonly.close() +})