介紹到這邊,相信細心的玩家已經發現了,測試案例必須以 async
宣告,而每一條操作、斷言,前方都必須加上 await
,這就是 Playwright 非常重要的 async / await
機制,為什麼需要這個機制呢?這個機制又解決了什麼問題呢?
想像一下,你的測試腳本具有極度快速的執行能力,然而,面對的遊戲世界 ─ 瀏覽器,它的速度很慢,需要時間發送網路請求、下載資源、渲染畫面、完成動畫等等,如果我們沒有加上 await
等待它準備好,會發生什麼災難呢?
// 錯誤示範,沒有 await
page.goto('https://example.com/login'); // 指令1: 前往登入頁
page.getByLabel('帳號').fill('ada'); // 指令2: 輸入帳號
page.getByLabel('密碼').fill('cat'); // 指令3: 輸入密碼
page.getByRole('button', { name: '登入' }).click(); // 指令4: 按下登入
雖然 Playwright 有 auto-waiting
機制,但如果按照上面的腳本,不加上 await
,那會在一瞬間將所有指令都丟給瀏覽器,當畫面還在載入頁面(小於 0.01 秒)時,就同時尋找帳號密碼輸入框要填寫帳號密碼,同時又按下登入按鈕,想想看,畫面還在 Loading,根本還沒出現輸入框以及按鈕,那麼測試就會壞掉啦!
所以,我們必須加上 async / await
,讓程式碼能夠一步一步按照順序執行,正確的步驟會是這種感覺:
如此一來,程式就會乖乖地按照腳本順序,一步步完成步驟。
同時,async/await 也增加了程式碼的可讀性,可以使程式碼看起來像同步,實際上卻能夠處理非同步,以往要處理非同步操作,我們必須使用 callback 或是 Promise,程式碼寫起來會是這樣:
// callback 版本
page.goto('https://example.com/login').then(() => {
page.getByLabel('帳號').fill('ada').then(() => {
page.getByLabel('密碼').fill('cat').then(() => {
page.getByRole('button', { name: '登入' }).click().then(() => {
console.log("登入成功!");
});
});
});
});
或是這樣:
// Promise chain 版本
page.goto('https://example.com/login')
.then(() => page.getByLabel('帳號').fill('ada'))
.then(() => page.getByLabel('密碼').fill('cat'))
.then(() => page.getByRole('button', { name: '登入' }).click())
.catch(err => {
console.error("測試失敗:", err);
});
讓我們來看看正確的寫法:
test('example', async() => {
page.goto('https://example.com/login');
page.getByLabel('帳號').fill('ada');
page.getByLabel('密碼').fill('cat');
page.getByRole('button', { name: '登入' }).click();
})
三個版本比較看看,callback 的寫法會造成 callback hell,Promise chain 雖然好讀許多,但免不了堆疊感,相較起來,使用 async / await
是不是更加的直覺、好懂呢?
到這裡,我們認識了 Playwright 的 async / await
機制,了解它如何讓動作能確實地依序施展,接下來,我們要更進一步揭開 auto-waiting
自動等待機制的神秘面紗,學習如何設定 waiting
來更精確地控制測試流程,讓你的測試腳本宛如傳說中的勇者,在戰鬥時精準掌握時機,在最完美的時刻施放技能。