Microsoftから公開されているオープンソースのE2Eテスト自動化フレームワークの
Playwright を使って、APEXアプリケーションのE2Eテストを実施してみます。
最終的には以下の作業を行います。作業環境には、macOSのpodmanでコンテナとして動作しているOracle APEXの環境を使っています。コード内の定数
BASE_URL を変更することで、テスト対象とするアプリケーションは変えられます。PlaywrightのテストはPlaywrightをインストールした上で、VSCodeの拡張機能Playwright Test for VSCodeから実行しています。
以下より、作業について説明します。
最初にE2Eテストの対象とするAPEXアプリケーションを作成します。
サンプル・アプリケーション のサンプル・データセット のEMP/DEPT をインストールして、APEXアプリケーションを作成します。言語 は英語 にします。テスト・コードではページのタイトルを参照しているため、言語を日本語にすると、確認のために指定している文字列も日本語にする必要があります。
SQLワークショップ のサンプル・データセット を開きます。
データ・セットEMP/DEPT のインストール (すでにインストール済みであれば更新 )をクリックします。
インストールする言語の指定を求められます。言語 として英語 を選択します。
すでに英語でインストール済みであれば、新規言語 は選択しません(- 新しい言語の選択 - から変更しません)。
次 へ進みます。
新規であればデータセットのインストール をクリックします。
更新であれば既存のデータセットのリフレッシュ をクリックします。
サンプル・データセットのロードが完了すると、そのデータを元にしたアプリケーションを作ることができます。
アプリケーションの作成 をクリックします。
アプリケーション作成ウィザード が開きます。ダッシュボード やファセット検索 のページが追加済みです。テスト用のアプリケーションに沢山のページは不要なので、機能についてはすべてチェックを外し ます。
以上でアプリケーションの作成 をクリックします。
アプリケーションが作成されます。
アプリケーションを実行し、今回のE2Eテストで行う操作を確認します。
アプリケーションにアクセスし、表示されるログイン・ページにて、以下の操作を行います。
ユーザー名を入力する。 パスワードを入力する。 サインインをクリックする。 サインインが完了すると、ホーム・ページが開きます。
ホーム・ページ上で以下の操作を行います。
Dashboardのカードをクリックし、Dashboardのページが開くことを確認します。 Employeesのカードをクリックし、Employeesのファセット検索のページが開くことを確認します。 Departmentsのカードをクリックし、Departmentsの対話モード・レポートのページが開くことを確認します。 Playwrightではセレクタを使って、操作する要素を特定します。
ログイン画面ではテキスト・フィールドにユーザー名とパスワードを入力し、ボタンのサインインをクリックしています。そのため、これらのテキスト・フィールドとボタンを特定するセレクタが必要です。
どのようなセレクタが適当か確認するために開発ツールの検証 を呼び出し、それぞれの要素のHTMLを確認します。
タイプがテキスト・フィールドの場合は、以下のようなHTMLが生成されていました。
<input type="text" id="P9999_USERNAME" name="P9999_USERNAME" placeholder="ユーザー名" class="text_field apex-item-text apex-item-has-icon" autocomplete="username" value="admin" size="40" maxlength="100" data-enter-submit="false" data-is-page-item-type="true">
ページ・アイテムの名前がid 属性として設定されているため、セレクタとしては#ページアイテム名 、つまり#P9999_USERNAME で特定できます。パスワードも同様に#P9999_PASSWORD で特定できます。
ボタンサインイン については、BUTTON要素を特定できる適当な属性がありません。
<button class="t-Button t-Button--hot lto18565948643221472_0" type="button" id="B18565948643221472" data-otel-label="LOGIN"><span class="t-Button-label">サインイン</span></button>
そのため、ボタンに静的ID としてLOGIN 設定します。
ボタンに設定した静的ID は属性id に設定されます。そのため、このボタンをセレクタ#LOGIN で特定できます。
<button class="t-Button t-Button--hot lto18565948643221472_0" type="button" id="LOGIN" data-otel-label="LOGIN"><span class="t-Button-label">サインイン</span></button>
ホーム・ページのカードについても確認します。
カードに紐ずくA要素を特定するセレクタとして使える、これといった属性がありません。
<a href="/ords/r/apexdev/demonstration-emp-dept106/dashboard?session=8435741618687" class="t-Card-wrap" data-otel-label="">
<div class="t-Card-icon u-color "><span class="t-Icon fa fa-dashboard"><span class="t-Card-initials" role="presentation"></span></span></div>
<div class="t-Card-titleWrap">
<h3 class="t-Card-title">Dashboard</h3>
<h4 class="t-Card-subtitle"></h4>
</div>
<div class="t-Card-body">
<div class="t-Card-desc"></div>
<div class="t-Card-info"></div>
</div>
<span class="t-Card-colorFill u-color " aria-hidden="true"></span>
</a>
ページの設定を確認します。
ページ・ナビゲーション のリージョンのタイプ はリスト です。
左ペインで
ページ共有コンポーネント を開き、リストに適用されているテンプレートを見つけ(テンプレート
Cards が適用されています)、
コンポーネントの編集 をクリックします。
このリージョンが参照しているHTMLのテンプレートが表示されます。
リスト・テンプレートCards のテンプレート定義 を確認します。
A要素はテンプレート定義 のカレント・リスト・テンプレート および非カレント・リスト・テンプレート で、以下のように記述されています。
<a href="#LINK#" class="t-Card-wrap" #A05# data-otel-label="#A10#">
置換文字列#A05# に、セレクタとして利用可能な属性を設定することにします。
置換文字列#A05# に設定する文字列は、Dashboard のカードにid="menu-dashboard" 、Employees にid="menu-employees" 、Departments にはid="menu-departments" とします。
ページ・ナビゲーションのソースを確認します。
ソース となるリスト はページ・ナビゲーション です。
リストのソースは共有コンポーネントとして作成されています。
共有コンポーネント のリスト を開きます。
リストの一覧にページ・ナビゲーション があります。名前のリンクをクリックして、ページ・ナビゲーション を開きます。
リスト・エントリとしてDashboard 、Employees 、Departments が含まれています。
最初にDashboard を開いて、属性を追加します。
テンプレート定義 に含まれる置換文字列#A01#から#A10#は、ユーザー定義属性 です。セクションのユーザー定義属性 に移動し、置換文字列#A05# に対応している5. Link Attributes としてid="menu-dashboard" を設定します。
変更の適用 をクリックします。
同様にリスト・エントリEmployees を開き、ユーザー定義属性 の5. Link Attributes としてid="menu-employees" を設定します。
リスト・エントリDepartments の5. Link Attributes にid=" menu-departments" を設定します。
以上で、Playwrightを使ってE2Eテストを行うために必要な、APEXアプリケーションの改変は完了です。
続いてPlaywrightをインストールします。PlaywrightのGetting StartedのInstallationの記述に従って、作業を行います。
いくつか方法がありますが、今回はnpmでインストールしました。
今回のE2Eテストを作成するディレクトリを作成します。本作業ではPlaywrightとしました。
mkdir Playwright
作成したディレクトリに移動し、Playwrightをインストールします。
cd Playwright
npm init playwright@latest
プロジェクトの初期化に当たって、いくつか質問されます。今回はすべてデフォルトの設定で初期化しています。デフォルトではテスト・スクリプトの記述はTypeScriptで行い、テスト・スクリプトはサブフォルダのtestsに保存されます。
% cd Playwright
Playwright % npm init playwright@latest
> npx
> create-playwright
Getting started with writing end-to-end tests with Playwright:
Initializing project in '.'
✔ Do you want to use TypeScript or JavaScript? · TypeScript
✔ Where to put your end-to-end tests? · tests
✔ Add a GitHub Actions workflow? (y/N) · false
✔ Install Playwright browsers (can be done manually via 'npx playwright install')? (Y/n) · true
Initializing NPM project (npm init -y)…
Wrote to /Users/ynakakoshi/Documents/Playwright/package.json:
{
"name": "playwright",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": ""
}
Installing Playwright Test (npm install --save-dev @playwright/test)…
added 3 packages, and audited 4 packages in 1s
found 0 vulnerabilities
Installing Types (npm install --save-dev @types/node)…
added 3 packages, and audited 7 packages in 640ms
found 0 vulnerabilities
Writing playwright.config.ts.
Writing tests/example.spec.ts.
Writing tests-examples/demo-todo-app.spec.ts.
Writing package.json.
Downloading browsers (npx playwright install)…
✔ Success! Created a Playwright Test project at /Users/ynakakoshi/Documents/Playwright
Inside that directory, you can run several commands:
npx playwright test
Runs the end-to-end tests.
npx playwright test --ui
Starts the interactive UI mode.
npx playwright test --project=chromium
Runs the tests only on Desktop Chrome.
npx playwright test example
Runs the tests in a specific file.
npx playwright test --debug
Runs the tests in debug mode.
npx playwright codegen
Auto generate tests with Codegen.
We suggest that you begin by typing:
npx playwright test
And check out the following files:
- ./tests/example.spec.ts - Example end-to-end test
- ./tests-examples/demo-todo-app.spec.ts - Demo Todo App end-to-end tests
- ./playwright.config.ts - Playwright Test configuration
Visit https://playwright.dev/docs/intro for more information. ✨
Happy hacking! 🎭
Playwright %
VSCodeに拡張機能のPlaywright Test for VSCode をインストールします。
VSCodeから先ほど作成したE2Eテストのプロジェクト・フォルダ(今回の作業ではPlaywright として作成)を開きます。プロジェクトが初期化されていて、雛形となるファイルが作成されています。
エクスプローラーからファイルplaywright.config.ts を選択し、以下の内容に置き換えます。chromiumのプロジェクトにviewportとstorageStateの設定を加えています。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { defineConfig , devices } from '@playwright/test' ;
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig ( {
testDir : './tests' ,
/* Run tests in files in parallel */
fullyParallel : true ,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly : ! ! process . env . CI ,
/* Retry on CI only */
retries : process . env . CI ? 2 : 0 ,
/* Opt out of parallel tests on CI. */
workers : process . env . CI ? 1 : undefined ,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter : 'html' ,
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use : {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://127.0.0.1:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace : 'on-first-retry' ,
} ,
/* Configure projects for major browsers */
projects : [
{
name : 'chromium' ,
use : {
...devices [ 'Desktop Chrome' ] ,
viewport : { width : 1280 , height : 1280 } ,
storageState : './.auth/state.json' ,
} ,
} ,
{
name : 'firefox' ,
use : { ...devices [ 'Desktop Firefox' ] } ,
} ,
{
name : 'webkit' ,
use : { ...devices [ 'Desktop Safari' ] } ,
} ,
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
] ,
/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// url: 'http://127.0.0.1:3000',
// reuseExistingServer: !process.env.CI,
// },
} ) ;
テスト・スクリプトを配置するフォルダtests に、以下のファイルfixtures.ts を作成します。このファイルに、APEXアプリケーションへサインインする手順を記述しています。
BASE_URL は、先ほど作成したAPEXアプリケーションを指すURLに変更します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { test as base } from "@playwright/test" ;
import path from 'path' ;
import * as fs from 'fs' ;
/*
* プロジェクトのフォルダ直下に.authフォルダを作成し、その中にauth.jsonを作成する。
* auth.jsonの中身は以下のようにする。
* ログイン・ユーザー名とパスワードは、実際のものに置き換えること。
* {"username":"admin","password":"4fae#ae2ef3!!","sessionId":"17291688537780"}
*
* 初回実行時のために、state.jsonファイルを作成しておく。
* echo "{}" > .auth/state.json
*
* BASE_URLは、テスト実行時に使用するURLを指定する。
*/
export const authFile = path . join ( __dirname , '../.auth/auth.json' ) ;
export const BASE_URL = 'http://localhost:8181/ords/r/apexdev/demonstration-emp-dept' ;
const stateFile = path . join ( __dirname , '../.auth/state.json' ) ;
export const test = base . extend ( {
// Create a custom fixture
loginUser : async ( { page } , use ) => {
// auth.jsonからユーザー名とパスワードを取り出す。
const auth = JSON . parse ( fs . readFileSync ( authFile , 'utf8' ) ) ;
/*
* 最初にホーム・ページにアクセスするが、URLにsession=が含まれていないため、ログイン・ページに遷移する。
*/
await page . goto ( `${ BASE_URL } /home` ) ;
/*
* ページ・作成ウィザードが生成したログイン・ページであれば、
* ページ・アイテム名がIDと同じINPUT要素が作成されている。
*/
await page . fill ( "input#P9999_USERNAME" , auth . username ) ;
await page . fill ( "input#P9999_PASSWORD" , auth . password ) ;
// await page.click("input#P9999_REMEMBER");
// サインイン・ボタンをクリックする。ボタンに静的IDとしてLOGINを設定する必要がある。
await page . click ( "button#LOGIN" ) ;
/*
* サインインに成功した後、ホーム・ページに遷移する。
* ページ・遷移後のURLからsessioin=の引数に設定されている
* セッションIDを取り出し、auth.jsonに保存する。
* このセッションIDを後続のテストで使用する。
*
* 完全にホーム・ページに遷移したことを確認するためにwaitForSelectorを呼び出しているが、
* もっと汎用的な方法があるはず。
*/
await page . waitForSelector ( '#menu-dashboard' ) ;
// URLからセッションIDを取り出す。
const homeURL = new URL ( page . url ( ) ) ;
const sessionId = homeURL . searchParams . get ( 'session' ) ;
// auth.jsonにsessionIdとして保存する。
const newAuth = { ...auth , sessionId : sessionId } ;
fs . writeFileSync ( authFile , JSON . stringify ( newAuth ) ) ;
// console.log('afer signin - auth', newAuth);
/*
* contextをstate.jsonに保存する。
*/
await page . context ( ) . storageState ( { path : stateFile } ) ;
// const cookies = await page.context().cookies();
// console.log('cookies', cookies);
// Pass control back to the test
await use ( ) ;
} ,
} ) ;
プロジェクトのフォルダの直下にフォルダ.auth を作成します。認証情報を保存するファイルauth.json と、ブラウザの状態を保存するファイルstate.json を配置します。
フォルダ.auth 直下にファイルauth.json を作成します。内容として、以下を記述します。username とpassword は環境に合わせて変更します。sessionId はログイン後にテスト・スクリプトによって更新されるため、値はそのままで問題ありません。
{"username":"admin","password":"4fae#ae2ef3!!","sessionId":"17291688537780"}
フォルダ.auth 直下にブラウザのステータスを保存するファイルstate.json を作成します。内容はとして{} を記述し、空のJSONオブジェクトが保存されている状態にします。
最後に今回実施するE2Eテストを記述したファイルapex.spec.ts を、フォルダtests以下に作成します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { expect } from '@playwright/test' ;
import { test , authFile , BASE_URL } from './fixtures.ts' ;
import path from 'path' ;
import * as fs from 'fs' ;
test ( 'open Dashboard' , async ( { page, loginUser } ) => {
/*
* fixtures.jsに記載されているloginUserが呼び出される。
* サインインに成功した後は、ホーム・ページが開かれている。
*/
// 表示されているページにダッシュボードのカードがあることを確認する。
await page . waitForSelector ( '#menu-dashboard' , { timeout : 5000 } ) ;
// ダッシュボードのカードをクリックする。#menu-dashboardはAPEX側に要設定。
await page . click ( "a#menu-dashboard" ) ;
// ダッシュボードのページが開かれていることを確認する。
await expect ( page ) . toHaveTitle ( 'Dashboard' ) ;
} ) ;
/*
test('verify', async ({ page }) => {
// call page without fixture.
await page.goto('http://localhost:8181/ords/apexdev/test/test');
});
*/
test ( 'open Employees' , async ( { page } ) => {
/*
* loginUserは呼び出さず、サインインが継続している状態から始める。
* auth.jsonに保存されているsessionIdを使用して、ホームページを開く。
* auth.jsonは、fixtures.jsのloginUserで、サインイン時にsessionIdが更新されている。
*/
const auth = JSON . parse ( fs . readFileSync ( authFile , 'utf8' ) ) ;
// ホーム・ページを開く。
const home_url = `${ BASE_URL } /home?session=${ auth . sessionId } ` ;
console . log ( home_url ) ;
await page . goto ( home_url ) ;
// ホーム・ページに従業員一覧のカードがあることを確認し、クリックする。#menu-employeesはAPEX側に要設定。
await page . waitForSelector ( '#menu-employees' , { timeout : 3000 } ) ;
await page . click ( "a#menu-employees" ) ;
// 従業員のページが開かれていることを確認する。
await expect ( page ) . toHaveTitle ( 'Employees' ) ;
} ) ;
test ( 'open Departments' , async ( { page } ) => {
/*
* 従業員一覧に同じ。
*/
const auth = JSON . parse ( fs . readFileSync ( authFile , 'utf8' ) ) ;
// ホーム・ページを開く。
const home_url = `${ BASE_URL } /home?session=${ auth . sessionId } ` ;
console . log ( home_url ) ;
await page . goto ( home_url ) ;
// ホーム・ページに部門一覧のカードがあることを確認し、クリックする。#menu-departmentsはAPEX側に要設定。
await page . waitForSelector ( '#menu-departments' , { timeout : 3000 } ) ;
await page . click ( "a#menu-departments" ) ;
// 部門のページが開かれていることを確認する。
await expect ( page ) . toHaveTitle ( 'Departments' ) ;
} ) ;
以上でPlaywrightを使ってE2Eテストを実行する準備ができました。
作成したファイルをすべて保存し、Playwrightの拡張機能を開きます。
testsとして認識されているapex.spec.tsにある実行ボタンをクリックすることで、apex.spec.tsに記述されている一連のテストが実行されます。apex.spec.tsにはopen Dashboard、open Employees、open Departmentsの3つのテストが含まれています。
それぞれ個別に実行できますが、open Dashboardでユーザー認証を行なっている都合上、最初にopen Dashboardのテストを実行する必要があります。
今回の記事は以上になります。
Oracle APEXのアプリケーション作成の参考になれば幸いです。