定位器
定位器 (locator) 是單一或多個元素的表示。每個定位器都由一個稱為選擇器 (selector) 的字串定義。Vitest 透過提供在幕後生成選擇器的便捷方法,來抽象化此選擇器。
定位器 API 使用 Playwright 的定位器 的一個分支,稱為 Ivya。然而,Vitest 將此 API 提供給每個 provider,而不僅限於 Playwright。
TIP
本頁涵蓋 API 用法。要更好地理解定位器及其用法,請閱讀 Playwright 的「定位器」文件。
getByRole
function getByRole(
role: ARIARole | string,
options?: LocatorByRoleOptions
): Locator;
建立一個定位器,透過元素的 ARIA 角色、ARIA 屬性 和 可存取名稱 來定位元素。
TIP
如果您只使用 getByText('The name')
查詢單一元素,通常建議使用 getByRole(expectedRole, { name: 'The name' })
。可存取名稱查詢不會取代其他查詢,例如 *ByAltText
或 *ByTitle
。雖然可存取名稱可能與這些屬性值相同,但它不會取代這些屬性的功能。
考慮以下 DOM 結構:
<h3>Sign up</h3>
<label>
Login
<input type="text" />
</label>
<label>
Password
<input type="password" />
</label>
<br />
<button>Submit</button>
您可以透過其隱含角色定位每個元素:
await expect
.element(page.getByRole('heading', { name: 'Sign up' }))
.toBeVisible();
await page.getByRole('textbox', { name: 'Login' }).fill('admin');
await page.getByRole('textbox', { name: 'Password' }).fill('admin');
await page.getByRole('button', { name: /submit/i }).click();
WARNING
角色是透過字串相等來匹配的,且不繼承 ARIA 角色層次結構。因此,查詢超類別角色(例如 checkbox
)將不包括具有子類別角色(例如 switch
)的元素。
預設情況下,HTML 中的許多語義元素都具有角色;例如,<input type="radio">
具有「radio」角色。HTML 中的非語義元素沒有角色;沒有添加語義的 <div>
和 <span>
返回 null
。role
屬性可以提供語義。
ARIA 指南強烈不鼓勵透過 role
或 aria-*
屬性為已經具有隱含角色的內建元素提供角色。
選項
exact: boolean
name
是否精確匹配:區分大小寫且完整字串。預設為禁用。如果name
是正規表達式,則此選項將被忽略。請注意,精確匹配仍會修剪空白字元。tsx<button>Hello World</button>; page.getByRole('button', { name: 'hello world' }); // ✅ page.getByRole('button', { name: 'hello world', exact: true }); // ❌ page.getByRole('button', { name: 'Hello World', exact: true }); // ✅
checked: boolean
是否應包含已選中的元素(由
aria-checked
或<input type="checkbox"/>
設定)。預設情況下,不會套用篩選器。有關更多資訊,請參閱
aria-checked
tsx<> <button role="checkbox" aria-checked="true" /> <input type="checkbox" checked /> </>; page.getByRole('checkbox', { checked: true }); // ✅ page.getByRole('checkbox', { checked: false }); // ❌
disabled: boolean
是否應包含已禁用的元素。預設情況下,不會套用篩選器。請注意,與其他屬性不同,
disabled
狀態會被繼承。有關更多資訊,請參閱
aria-disabled
tsx<input type="text" disabled />; page.getByRole('textbox', { disabled: true }); // ✅ page.getByRole('textbox', { disabled: false }); // ❌
expanded: boolean
是否應包含已展開的元素。預設情況下,不會套用篩選器。
有關更多資訊,請參閱
aria-expanded
tsx<a aria-expanded="true" href="example.com"> Link </a>; page.getByRole('link', { expanded: true }); // ✅ page.getByRole('link', { expanded: false }); // ❌
includeHidden: boolean
是否應查詢通常會從可存取性樹中排除的元素。預設情況下,只有非隱藏元素會被角色選擇器匹配。
請注意,角色
none
和presentation
總是會被包含。tsx<button style="display: none" />; page.getByRole('button'); // ❌ page.getByRole('button', { includeHidden: false }); // ❌ page.getByRole('button', { includeHidden: true }); // ✅
level: number
一個數字屬性,通常用於
heading
、listitem
、row
、treeitem
角色,對於<h1>-<h6>
元素具有預設值。預設情況下,不會套用篩選器。有關更多資訊,請參閱
aria-level
tsx<> <h1>Heading Level One</h1> <div role="heading" aria-level="1"> Second Heading Level One </div> </>; page.getByRole('heading', { level: 1 }); // ✅ page.getByRole('heading', { level: 2 }); // ❌
name: string | RegExp
可存取名稱。預設情況下,匹配不區分大小寫並尋找子字串。使用
exact
選項來控制此行為。tsx<button>Click Me!</button>; page.getByRole('button', { name: 'Click Me!' }); // ✅ page.getByRole('button', { name: 'click me!' }); // ✅ page.getByRole('button', { name: 'Click Me?' }); // ❌
pressed: boolean
是否應包含已按下的元素。預設情況下,不會套用篩選器。
有關更多資訊,請參閱
aria-pressed
tsx<button aria-pressed="true">👍</button>; page.getByRole('button', { pressed: true }); // ✅ page.getByRole('button', { pressed: false }); // ❌
selected: boolean
是否應包含已選取的元素。預設情況下,不會套用篩選器。
有關更多資訊,請參閱
aria-selected
tsx<button role="tab" aria-selected="true"> Vue </button>; page.getByRole('button', { selected: true }); // ✅ page.getByRole('button', { selected: false }); // ❌
另請參閱
getByAltText
function getByAltText(text: string | RegExp, options?: LocatorOptions): Locator;
建立一個定位器,能夠找到具有匹配 alt
屬性文字的元素。與 testing-library 的實作方式不同,Vitest 將匹配任何具有匹配 alt
屬性的元素。
<img alt="Incredibles 2 Poster" src="/incredibles-2.png" />;
page.getByAltText(/incredibles.*? poster/i); // ✅
page.getByAltText('non existing alt text'); // ❌
選項
exact: boolean
text
是否精確匹配:區分大小寫且完整字串。預設為禁用。如果text
是正規表達式,則此選項將被忽略。請注意,精確匹配仍會修剪空白字元。
另請參閱
getByLabelText
function getByLabelText(
text: string | RegExp,
options?: LocatorOptions
): Locator;
建立一個定位器,能夠找到具有相關聯標籤的元素。
page.getByLabelText('Username')
定位器將在以下範例中找到所有輸入元素:
// for/htmlFor 標籤與表單元素 id 之間的關係
<label for="username-input">Username</label>
<input id="username-input" />
// 帶有表單元素的 aria-labelledby 屬性
<label id="username-label">Username</label>
<input aria-labelledby="username-label" />
// 包裹標籤
<label>Username <input /></label>
// 標籤文字位於另一個子元素中的包裹標籤
<label>
<span>Username</span>
<input />
</label>
// aria-label 屬性
// 請注意,這不是使用者可以在頁面上看到的標籤,因此您的輸入目的應對視覺使用者來說顯而易見。
<input aria-label="Username" />
選項
exact: boolean
text
是否精確匹配:區分大小寫且完整字串。預設為禁用。如果text
是正規表達式,則此選項將被忽略。請注意,精確匹配仍會修剪空白字元。
另請參閱
getByPlaceholder
function getByPlaceholder(
text: string | RegExp,
options?: LocatorOptions
): Locator;
建立一個定位器,能夠找到具有指定 placeholder
屬性元素的定位器。Vitest 將匹配任何具有匹配 placeholder
屬性的元素,而不僅僅是 input
。
<input placeholder="Username" />;
page.getByPlaceholder('Username'); // ✅
page.getByPlaceholder('not found'); // ❌
WARNING
通常,建議使用 getByLabelText
來依賴標籤,而非佔位符。
選項
exact: boolean
text
是否精確匹配:區分大小寫且完整字串。預設為禁用。如果text
是正規表達式,則此選項將被忽略。請注意,精確匹配仍會修剪空白字元。
另請參閱
getByText
function getByText(text: string | RegExp, options?: LocatorOptions): Locator;
建立一個定位器,能夠找到包含指定文字的元素。文字將與 TextNode 的 nodeValue
或輸入的 value
(如果類型為 button
或 reset
)進行匹配。透過文字匹配總是會將空白字元標準化,即使是精確匹配也是如此。例如,它會將多個空格變成一個,將換行符轉換為空格,並忽略開頭和結尾的空白字元。
<a href="/about">About ℹ️</a>;
page.getByText(/about/i); // ✅
page.getByText('about', { exact: true }); // ❌
TIP
此定位器有助於定位非互動式元素。如果您需要定位互動式元素,例如按鈕或輸入,請優先使用 getByRole
。
選項
exact: boolean
text
是否精確匹配:區分大小寫且完整字串。預設為禁用。如果text
是正規表達式,則此選項將被忽略。請注意,精確匹配仍會修剪空白字元。
另請參閱
getByTitle
function getByTitle(text: string | RegExp, options?: LocatorOptions): Locator;
建立一個定位器,能夠找到具有指定 title
屬性元素的定位器。與 testing-library 的 getByTitle
不同之處在於,Vitest 無法在 SVG 中找到 title
元素。
<span title="Delete" id="2"></span>;
page.getByTitle('Delete'); // ✅
page.getByTitle('Create'); // ❌
選項
exact: boolean
text
是否精確匹配:區分大小寫且完整字串。預設為禁用。如果text
是正規表達式,則此選項將被忽略。請注意,精確匹配仍會修剪空白字元。
另請參閱
getByTestId
function getByTestId(text: string | RegExp): Locator;
建立一個定位器,能夠找到匹配指定測試 ID 屬性元素的定位器。您可以使用 browser.locators.testIdAttribute
配置屬性名稱。
<div data-testid="custom-element" />;
page.getByTestId('custom-element'); // ✅
page.getByTestId('non-existing-element'); // ❌
WARNING
建議僅在其他定位器不適用於您的使用情境時才使用此方法。使用 data-testid
屬性不符合您的軟體使用方式,應盡可能避免。
選項
exact: boolean
text
是否精確匹配:區分大小寫且完整字串。預設為禁用。如果text
是正規表達式,則此選項將被忽略。請注意,精確匹配仍會修剪空白字元。
另請參閱
nth
function nth(index: number): Locator;
此方法返回一個新的定位器,該定位器僅匹配多元素查詢結果中的特定索引。它是基於零的索引,nth(0)
選擇第一個元素。與 elements()[n]
不同,nth
定位器將被重試直到元素存在。
<div aria-label="one"><input /><input /><input /></div>
<div aria-label="two"><input /></div>
page.getByRole('textbox').nth(0); // ✅
page.getByRole('textbox').nth(4); // ❌
TIP
在使用 nth
之前,您可能會發現使用鏈式定位器來縮小搜尋範圍很有用。 有時沒有更好的方法來區分,只能透過元素位置;儘管這可能導致測試不穩定 (flake),但總比沒有好。
page.getByLabel('two').getByRole('input'); // ✅ 比 page.getByRole('textbox').nth(3) 更好的替代方案
page.getByLabel('one').getByRole('input'); // ❌ 太模糊
page.getByLabel('one').getByRole('input').nth(1); // ✅ 務實的折衷方案
first
function first(): Locator;
此方法返回一個新的定位器,該定位器僅匹配多元素查詢結果的第一個索引。 它是 nth(0)
的簡寫。
<input /> <input /> <input />
page.getByRole('textbox').first(); // ✅
last
function last(): Locator;
此方法返回一個新的定位器,該定位器僅匹配多元素查詢結果的最後一個索引。 它是 nth(-1)
的簡寫。
<input /> <input /> <input />
page.getByRole('textbox').last(); // ✅
and
function and(locator: Locator): Locator;
此方法建立一個新的定位器,該定位器同時符合父定位器和提供的定位器。以下範例找到一個具有特定標題的按鈕:
page.getByRole('button').and(page.getByTitle('Subscribe'));
or
function or(locator: Locator): Locator;
此方法建立一個新的定位器,該定位器符合其中一個或兩個定位器。
WARNING
請注意,若定位器匹配多個元素,呼叫其他方法可能會拋出錯誤,如果它期望單一元素:
<>
<button>Click me</button>
<a href="https://vitest.dev">Error happened!</a>
</>;
page.getByRole('button').or(page.getByRole('link')).click(); // ❌ 匹配多個元素
filter
function filter(options: LocatorOptions): Locator;
此方法根據選項(例如按文字篩選)限縮定位器範圍。它可以串聯以套用多個篩選器。
has
- 類型:
Locator
此選項縮小選擇器範圍,以匹配包含符合所提供定位器之其他元素的元素。例如,對於此 HTML:
<article>
<div>Vitest</div>
</article>
<article>
<div>Rolldown</div>
</article>
我們可以縮小定位器範圍,只找到內部包含 Vitest
文字的 article
:
page.getByRole('article').filter({ has: page.getByText('Vitest') }); // ✅
WARNING
提供的定位器(範例中的 page.getByText('Vitest')
)必須相對於父定位器(範例中的 page.getByRole('article')
)。它將從父定位器開始查詢,而不是文件根元素。
這意味著您不能傳入查詢父定位器外部元素的定位器:
page.getByText('Vitest').filter({ has: page.getByRole('article') }); // ❌
此範例將會失敗,因為 article
元素位於帶有 Vitest
文字的元素之外。
TIP
此方法可以串聯以進一步限縮元素範圍:
page
.getByRole('article')
.filter({ has: page.getByRole('button', { name: 'delete row' }) })
.filter({ has: page.getByText('Vitest') });
hasNot
- 類型:
Locator
此選項縮小選擇器範圍,以匹配不包含符合所提供定位器之其他元素的元素。例如,對於此 HTML:
<article>
<div>Vitest</div>
</article>
<article>
<div>Rolldown</div>
</article>
我們可以縮小定位器範圍,只找到內部不包含 Rolldown
的 article
。
page.getByRole('article').filter({ hasNot: page.getByText('Rolldown') }); // ✅
page.getByRole('article').filter({ hasNot: page.getByText('Vitest') }); // ❌
WARNING
請注意,提供的定位器是針對父元素進行查詢的,而不是文件根目錄,就像 has
選項一樣。
hasText
- 類型:
string | RegExp
此選項縮小選擇器範圍,以僅匹配內部某處包含所提供文字的元素。當傳入 string
時,匹配不區分大小寫並尋找子字串。
<article>
<div>Vitest</div>
</article>
<article>
<div>Rolldown</div>
</article>
兩個定位器都將找到相同的元素,因為搜尋不區分大小寫:
page.getByRole('article').filter({ hasText: 'Vitest' }); // ✅
page.getByRole('article').filter({ hasText: 'Vite' }); // ✅
hasNotText
- 類型:
string | RegExp
此選項縮小選擇器範圍,以僅匹配內部某處不包含所提供文字的元素。當傳入 string
時,匹配不區分大小寫並尋找子字串。
方法
所有方法都是非同步的,必須使用 await
關鍵字等待其完成。從 Vitest 3 開始,如果方法沒有被等待,測試將失敗。
click
function click(options?: UserEventClickOptions): Promise<void>;
點擊元素。您可以使用選項來指定游標位置。
import { page } from '@vitest/browser/context';
await page.getByRole('img', { name: 'Rose' }).click();
dblClick
function dblClick(options?: UserEventDoubleClickOptions): Promise<void>;
觸發元素的雙擊事件。您可以使用選項來指定游標位置。
import { page } from '@vitest/browser/context';
await page.getByRole('img', { name: 'Rose' }).dblClick();
tripleClick
function tripleClick(options?: UserEventTripleClickOptions): Promise<void>;
觸發元素的三次點擊事件。由於瀏覽器 API 中並無 tripleclick
事件,此方法將連續觸發三次點擊事件。
import { page } from '@vitest/browser/context';
await page.getByRole('img', { name: 'Rose' }).tripleClick();
clear
function clear(options?: UserEventClearOptions): Promise<void>;
清除輸入元素的內容。
import { page } from '@vitest/browser/context';
await page.getByRole('textbox', { name: 'Full Name' }).clear();
hover
function hover(options?: UserEventHoverOptions): Promise<void>;
將游標移動到所選元素。
import { page } from '@vitest/browser/context';
await page.getByRole('img', { name: 'Rose' }).hover();
unhover
function unhover(options?: UserEventHoverOptions): Promise<void>;
這與 locator.hover
的工作方式相同,但將游標移動到 document.body
元素。
import { page } from '@vitest/browser/context';
await page.getByRole('img', { name: 'Rose' }).unhover();
fill
function fill(text: string, options?: UserEventFillOptions): Promise<void>;
設定當前 input
、textarea
或 contenteditable
元素的值。
import { page } from '@vitest/browser/context';
await page.getByRole('input', { name: 'Full Name' }).fill('Mr. Bean');
dropTo
function dropTo(
target: Locator,
options?: UserEventDragAndDropOptions
): Promise<void>;
將當前元素拖曳到目標位置。
import { page } from '@vitest/browser/context';
const paris = page.getByText('Paris');
const france = page.getByText('France');
await paris.dropTo(france);
selectOptions
function selectOptions(
values: HTMLElement | HTMLElement[] | Locator | Locator[] | string | string[],
options?: UserEventSelectOptions
): Promise<void>;
從 <select>
元素中選擇一個或多個值。
import { page } from '@vitest/browser/context';
const languages = page.getByRole('select', { name: 'Languages' });
await languages.selectOptions('EN');
await languages.selectOptions(['ES', 'FR']);
await languages.selectOptions([
languages.getByRole('option', { name: 'Spanish' }),
languages.getByRole('option', { name: 'French' }),
]);
screenshot
function screenshot(
options: LocatorScreenshotOptions & { save: false }
): Promise<string>;
function screenshot(
options: LocatorScreenshotOptions & { base64: true }
): Promise<{
path: string;
base64: string;
}>;
function screenshot(
options?: LocatorScreenshotOptions & { base64?: false }
): Promise<string>;
建立符合定位器選擇器之元素的螢幕截圖。
您可以使用 path
選項指定螢幕截圖的儲存位置,該位置相對於當前的測試檔案。如果未設定 path
選項,Vitest 將預設使用 browser.screenshotDirectory
(預設為 __screenshot__
),以及檔案和測試的名稱來決定螢幕截圖的檔案路徑。
如果您還需要螢幕截圖的內容,您可以指定 base64: true
以將其與螢幕截圖儲存所在的檔案路徑一起返回。
import { page } from '@vitest/browser/context';
const button = page.getByRole('button', { name: 'Click Me!' });
const path = await button.screenshot();
const { path, base64 } = await button.screenshot({
path: './button-click-me.png',
base64: true, // 也返回 base64 字串
});
// path - 螢幕截圖的完整檔案路徑
// base64 - 螢幕截圖的 Base64 編碼字串
警告 3.2.0+
請注意,如果 save
設定為 false
,screenshot
將始終返回 Base64 字串。 在這種情況下,path
也會被忽略。
query
function query(): Element | null;
此方法返回匹配定位器選擇器的單一元素,如果未找到元素則返回 null
。
如果多個元素匹配選擇器,此方法將拋出錯誤。當您需要所有符合的 DOM 元素時,請使用 .elements()
,或者當您需要符合選擇器的定位器陣列時,請使用 .all()
。
考慮以下 DOM 結構:
<div>Hello <span>World</span></div>
<div>Hello</div>
這些定位器不會拋出錯誤:
page.getByText('Hello World').query(); // ✅ HTMLDivElement
page.getByText('Hello Germany').query(); // ✅ null
page.getByText('World').query(); // ✅ HTMLSpanElement
page.getByText('Hello', { exact: true }).query(); // ✅ HTMLSpanElement
這些定位器將拋出錯誤:
// 返回多個元素
page.getByText('Hello').query(); // ❌
page.getByText(/^Hello/).query(); // ❌
element
function element(): Element;
此方法返回匹配定位器選擇器的單一元素。
如果沒有元素匹配選擇器,則會拋出錯誤。當您只需要檢查元素是否存在時,請考慮使用 .query()
。
如果多個元素匹配選擇器,則會拋出錯誤。當您需要所有符合的 DOM 元素時,請使用 .elements()
,或者當您需要符合選擇器的定位器陣列時,請使用 .all()
。
TIP
如果您需要將此方法傳遞給外部函式庫,它會很有用。當定位器與 expect.element
一起使用時,每次斷言重試時都會自動呼叫它:
await expect.element(page.getByRole('button')).toBeDisabled();
考慮以下 DOM 結構:
<div>Hello <span>World</span></div>
<div>Hello Germany</div>
<div>Hello</div>
這些定位器不會拋出錯誤:
page.getByText('Hello World').element(); // ✅
page.getByText('Hello Germany').element(); // ✅
page.getByText('World').element(); // ✅
page.getByText('Hello', { exact: true }).element(); // ✅
這些定位器將拋出錯誤:
// 返回多個元素
page.getByText('Hello').element(); // ❌
page.getByText(/^Hello/).element(); // ❌
// 返回沒有元素
page.getByText('Hello USA').element(); // ❌
elements
function elements(): Element[];
此方法返回匹配定位器選擇器的元素陣列。
此函數從不拋出錯誤。如果沒有任何元素匹配選擇器,此方法將返回一個空陣列。
考慮以下 DOM 結構:
<div>Hello <span>World</span></div>
<div>Hello</div>
這些定位器將總是成功執行:
page.getByText('Hello World').elements(); // ✅ [HTMLElement]
page.getByText('World').elements(); // ✅ [HTMLElement]
page.getByText('Hello', { exact: true }).elements(); // ✅ [HTMLElement]
page.getByText('Hello').element(); // ✅ [HTMLElement, HTMLElement]
page.getByText('Hello USA').elements(); // ✅ []
all
function all(): Locator[];
此方法返回一個匹配選擇器的新定位器陣列。
在內部,此方法呼叫 .elements
並使用 page.elementLocator
包裝每個元素。
屬性
selector
selector
是一個字串,將用於讓瀏覽器提供者定位元素。Playwright 將使用 playwright
定位器語法,而 preview
和 webdriverio
將使用 CSS。
DANGER
您不應該在測試程式碼中使用此字串。selector
字串僅應在處理命令 API 時使用:
import type { BrowserCommand } from 'vitest/node';
const test: BrowserCommand<string> = function test(context, selector) {
// playwright
await context.iframe.locator(selector).click();
// webdriverio
await context.browser.$(selector).click();
};
import { test } from 'vitest';
import { commands, page } from '@vitest/browser/context';
test('works correctly', async () => {
await commands.test(page.getByText('Hello').selector); // ✅
// vitest 將自動將其解構為字串
await commands.test(page.getByText('Hello')); // ✅
});
自訂定位器 3.2.0+ 進階
您可以透過定義定位器工廠物件來擴充內建的定位器 API。這些方法將作為 page
物件和任何已建立定位器的方法。
如果內建定位器不夠用,這些定位器會很有用。例如,當您為 UI 使用自訂框架時。
定位器工廠必須返回一個選擇器字串或定位器本身。
TIP
選擇器語法與 Playwright 定位器相同。請閱讀他們的指南以更深入地了解如何使用它們。
import { locators } from '@vitest/browser/context';
locators.extend({
getByArticleTitle(title) {
return `[data-title="${title}"]`;
},
getByArticleCommentsCount(count) {
return `.comments :text("${count} comments")`;
},
async previewComments() {
// 您可以透過「this」存取當前的定位器
// 請注意,如果該方法是在 `page` 物件上呼叫的,則 `this` 將是 `page`,
// 而不是定位器!
if (this !== page) {
await this.click();
}
// ...
},
});
// 如果您使用 TypeScript,您可以擴展 LocatorSelectors 介面
// 以在 locators.extend、page.* 和 locator.* 方法中啟用自動完成功能
declare module '@vitest/browser/context' {
interface LocatorSelectors {
// 如果自訂方法返回字串,它將被自動轉換為定位器
// 如果它返回其他類型的值,那麼它將照常返回
getByArticleTitle(title: string): Locator;
getByArticleCommentsCount(count: number): Locator;
// Vitest 將返回一個 Promise,並且不會嘗試將其轉換為定位器
previewComments(this: Locator): Promise<void>;
}
}
如果該方法是在全域 page
物件上呼叫,則選擇器將應用於整個頁面。在下面的範例中,getByArticleTitle
將會找到所有具有 data-title
屬性且值為 title
的元素。但是,如果該方法是在定位器上呼叫,則它將僅限於該定位器。
<article data-title="Hello, World!">
Hello, World!
<button id="comments">2 comments</button>
</article>
<article data-title="Hello, Vitest!">
Hello, Vitest!
<button id="comments">0 comments</button>
</article>
const articles = page.getByRole('article');
const worldArticle = page.getByArticleTitle('Hello, World!'); // ✅
const commentsElement = worldArticle.getByArticleCommentsCount(2); // ✅
const wrongCommentsElement = worldArticle.getByArticleCommentsCount(0); // ❌
const wrongElement = page.getByArticleTitle('No Article!'); // ❌
await commentsElement.previewComments(); // ✅
await wrongCommentsElement.previewComments(); // ❌