Playwright MCP
- The MCP server for playwright can be installed by the following
"playwright": {
"command": "npx",
"args": [
"@playwright/mcp@latest"
]
}
- Important Tools
browser_evaluate
browser_run_code_unsafe
- Run arbitrary Playwright code in server process
browser_snapshot
- Capture accessibility snapshot of the current page
- Navigation
browser_navigate
browser_navigate_back
- Other tools
browser_close
browser_resize
browser_console_messages (Return all console messages)
browser_file_upload (Upload one or multiple files)
browser_drop (Drop files onto an element)
browser_fill_form
browser_press_key (press key on keyboard)
browser_type (Type text on element)
browser_network_requests (List of network requests)
browser_network_request (Details of single network request)
browser_take_screenshot
browser_click
browser_drag
browser_hover
browser_select_option
browser_tabs
browser_wait_for
Playwright MCP Analysis
use playwright mcp and open youtube.com, find out the locator for search bar as well as the raw html surrounding it
Step 1
- Tool:
browser_navigate
- Input
{
"url": "https://www.youtube.com/"
}
### Ran Playwright code
```js
await page.goto('https://www.youtube.com/');
```
### Page
- Page URL: https://www.youtube.com/
- Page Title: YouTube
### Snapshot
- [Snapshot](.playwright-mcp/page-2026-06-20T12-31-33-598Z.yml)
### Events
- New console entries: .playwright-mcp/console-2026-06-20T12-31-29-295Z.log#L1-L3
Step 2
- Tool:
browser_snapshot
- Input
{
"depth": 6
}
### Page
- Page URL: https://www.youtube.com/
- Page Title: YouTube
- Console: 6 errors, 3 warnings
### Snapshot
```yaml
- generic [ref=e2]:
- banner [ref=e4]:
- generic [ref=e6]:
- generic [ref=e7]:
- button "Guide" [ref=e9] [cursor=pointer]:
- generic [ref=e12]:
- img
- generic [ref=e13]:
- link "YouTube Home" [ref=e14] [cursor=pointer]:
- /url: /
- generic [ref=e19]
- generic [ref=e20]: IN
- button "Skip navigation" [ref=e24] [cursor=pointer]:
- generic [ref=e25]: Skip navigation
- generic [ref=e29]:
- search [ref=e30]:
- generic [ref=e31]:
- combobox "Search" [expanded] [ref=e34]
- button "Search" [ref=e35] [cursor=pointer]
- generic [ref=e40]:
- button "Search with your voice" [ref=e42] [cursor=pointer]:
- generic [ref=e46]
- tooltip "tooltip"
- generic [ref=e51]:
- button "Settings" [ref=e56] [cursor=pointer]:
- generic [ref=e59]:
- img
- link "Sign in" [ref=e62] [cursor=pointer]:
- /url: https://accounts.google.com/ServiceLogin?service=youtube&uilel=3&passive=true&continue=https%3A%2F%2Fwww.youtube.com%2Fsignin%3Faction_handle_signin%3Dtrue%26app%3Ddesktop%26hl%3Den%26next%3Dhttps%253A%252F%252Fwww.youtube.com%252F&hl=en&ec=65620
- generic [ref=e66]:
- img
- generic [ref=e67]: Sign in
- navigation [ref=e71]:
- generic [ref=e72]:
- link "Home" [ref=e74] [cursor=pointer]:
- /url: /
- generic [ref=e77]:
- img
- generic [ref=e78]: Home
- link "Shorts" [ref=e80] [cursor=pointer]:
- /url: /shorts/
- generic [ref=e83]:
- img
- generic [ref=e84]: Shorts
- link "Subscriptions" [ref=e86] [cursor=pointer]:
- /url: /feed/subscriptions
- generic [ref=e89]:
- img
- generic [ref=e90]: Subscriptions
- link "You" [ref=e92] [cursor=pointer]:
- /url: /feed/you
- generic [ref=e95]:
- img
- generic [ref=e96]: You
- generic [ref=e97]:
- main [ref=e98]:
- generic [ref=e108]:
- heading "Try searching to get started" [level=2] [ref=e109]
- generic [ref=e110]: Start watching videos to help us build a feed of videos you'll love.
- text: •
```
### Events
- New console entries: .playwright-mcp/console-2026-06-20T12-31-29-295Z.log#L4-L12
- Analysis
- The output exposes
combobox "Search" [expanded] [ref=e34] which agent can use
Step 3
- Tool:
browser_evaluate
- Input
target: Target element reference from page snapshot or unique element selector
element: Human readable element description
function: Can take the element as input which is referred by target, Run JS on browser
{
"target": "e34",
"element": "YouTube search input combobox",
"function": "(element) => {\n const parent = element.parentElement;\n const grandParent = parent ? parent.parentElement : null;\n const searchRoot = element.closest('ytd-searchbox, search, #search, form');\n return {\n tag: element.tagName,\n id: element.id,\n name: element.getAttribute('name'),\n ariaLabel: element.getAttribute('aria-label'),\n outerHTML: element.outerHTML,\n parentOuterHTML: parent ? parent.outerHTML : null,\n grandParentOuterHTML: grandParent ? grandParent.outerHTML : null,\n searchRootOuterHTML: searchRoot ? searchRoot.outerHTML : null\n };\n}"
}
(element) => {
const parent = element.parentElement;
const grandParent = parent ? parent.parentElement : null;
const searchRoot = element.closest('ytd-searchbox, search, #search, form');
return {
tag: element.tagName,
id: element.id,
name: element.getAttribute('name'),
ariaLabel: element.getAttribute('aria-label'),
outerHTML: element.outerHTML,
parentOuterHTML: parent ? parent.outerHTML : null,
grandParentOuterHTML: grandParent ? grandParent.outerHTML : null,
searchRootOuterHTML: searchRoot ? searchRoot.outerHTML : null
};
}
### Result
{
"tag": "INPUT",
"id": "",
"name": "search_query",
"ariaLabel": null,
"outerHTML": "<input class=\"ytSearchboxComponentInput yt-searchbox-input title\" name=\"search_query\" aria-controls=\"i0\" aria-expanded=\"true\" type=\"text\" autocomplete=\"off\" autocorrect=\"off\" spellcheck=\"false\" aria-autocomplete=\"list\" role=\"combobox\" placeholder=\"Search\">",
"parentOuterHTML": "<form action=\"/results\" class=\"ytSearchboxComponentSearchForm\"><input class=\"ytSearchboxComponentInput yt-searchbox-input title\" name=\"search_query\" aria-controls=\"i0\" aria-expanded=\"true\" type=\"text\" autocomplete=\"off\" autocorrect=\"off\" spellcheck=\"false\" aria-autocomplete=\"list\" role=\"combobox\" placeholder=\"Search\"></form>",
"grandParentOuterHTML": "<div class=\"ytSearchboxComponentInputBox\"><form action=\"/results\" class=\"ytSearchboxComponentSearchForm\"><input class=\"ytSearchboxComponentInput yt-searchbox-input title\" name=\"search_query\" aria-controls=\"i0\" aria-expanded=\"true\" type=\"text\" autocomplete=\"off\" autocorrect=\"off\" spellcheck=\"false\" aria-autocomplete=\"list\" role=\"combobox\" placeholder=\"Search\"></form></div>",
"searchRootOuterHTML": "<form action=\"/results\" class=\"ytSearchboxComponentSearchForm\"><input class=\"ytSearchboxComponentInput yt-searchbox-input title\" name=\"search_query\" aria-controls=\"i0\" aria-expanded=\"true\" type=\"text\" autocomplete=\"off\" autocorrect=\"off\" spellcheck=\"false\" aria-autocomplete=\"list\" role=\"combobox\" placeholder=\"Search\"></form>"
}
### Ran Playwright code
```js
await page.getByRole('combobox', { name: 'Search' }).evaluate('(element) => {\n const parent = element.parentElement;\n const grandParent = parent ? parent.parentElement : null;\n const searchRoot = element.closest(\'ytd-searchbox, search, #search, form\');\n return {\n tag: element.tagName,\n id: element.id,\n name: element.getAttribute(\'name\'),\n ariaLabel: element.getAttribute(\'aria-label\'),\n outerHTML: element.outerHTML,\n parentOuterHTML: parent ? parent.outerHTML : null,\n grandParentOuterHTML: grandParent ? grandParent.outerHTML : null,\n searchRootOuterHTML: searchRoot ? searchRoot.outerHTML : null\n };\n}');
```
### Page
- Page URL: https://www.youtube.com/
- Page Title: YouTube
- Console: 6 errors, 4 warnings
### Events
- New console entries: .playwright-mcp/console-2026-06-20T12-31-29-295Z.log#L13
- Analysis
- Using
ref=34, Agent attempts to get the HTML of root, parent, grandparent
- It attempts to find the closest match for the following
ytd-searchbox (Guessed by Agent)
search (Guessed by Agent as semantic element possible)
#search (Guessed by Agent as common legacy ID)
form (Generic Fallback)
- The closest match happens to be
form and forms the root
Step 4
- Tool:
browser_evaluate
- Input
{
"function": "() => {\n const selectors = [\n 'input[name=\"search_query\"]',\n 'input.yt-searchbox-input',\n 'form.ytSearchboxComponentSearchForm input[name=\"search_query\"]',\n 'input[placeholder=\"Search\"][role=\"combobox\"]'\n ];\n const counts = Object.fromEntries(selectors.map((s) => [s, document.querySelectorAll(s).length]));\n return counts;\n}"
}
() => {
const selectors = [
'input[name="search_query"]',
'input.yt-searchbox-input',
'form.ytSearchboxComponentSearchForm input[name="search_query"]',
'input[placeholder="Search"][role="combobox"]'
];
const counts = Object.fromEntries(selectors.map((s) => [s, document.querySelectorAll(s).length]));
return counts;
}
### Result
{
"input[name=\"search_query\"]": 1,
"input.yt-searchbox-input": 1,
"form.ytSearchboxComponentSearchForm input[name=\"search_query\"]": 1,
"input[placeholder=\"Search\"][role=\"combobox\"]": 1
}
### Ran Playwright code
```js
await page.evaluate('() => {\n const selectors = [\n \'input[name="search_query"]\',\n \'input.yt-searchbox-input\',\n \'form.ytSearchboxComponentSearchForm input[name="search_query"]\',\n \'input[placeholder="Search"][role="combobox"]\'\n ];\n const counts = Object.fromEntries(selectors.map((s) => [s, document.querySelectorAll(s).length]));\n return counts;\n}');
```
Output
- Best locator options
page.getByRole("combobox", { name: "Search" }): Taken from Step 2
input[name="search_query"]: Taken from Step 3
form.ytSearchboxComponentSearchForm input[name="search_query"] Taken from Step 3
- Validation
- Raw HTML
Opened YouTube with Playwright MCP and inspected the live DOM for the search field.
Best locator options
- Playwright role locator: page.getByRole("combobox", { name: "Search" })
- CSS (recommended stable): input[name="search_query"]
- CSS (scoped): form.ytSearchboxComponentSearchForm input[name="search_query"]
Validation
- Each selector above matched exactly 1 element on the current page.
Raw HTML around the search bar
- Input element:
```html
<input class="ytSearchboxComponentInput yt-searchbox-input title" name="search_query" aria-controls="i0" aria-expanded="true" type="text" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" placeholder="Search">
```
- Immediate parent:
```html
<form action="/results" class="ytSearchboxComponentSearchForm"><input class="ytSearchboxComponentInput yt-searchbox-input title" name="search_query" aria-controls="i0" aria-expanded="true" type="text" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" placeholder="Search"></form>
```
- One level higher:
```html
<div class="ytSearchboxComponentInputBox"><form action="/results" class="ytSearchboxComponentSearchForm"><input class="ytSearchboxComponentInput yt-searchbox-input title" name="search_query" aria-controls="i0" aria-expanded="true" type="text" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" placeholder="Search"></form></div>
```
Playwright Test MCP
npx playwright init-agents --loop=vscode
- They internally use playwright test MCP server
"playwright-test": {
"type": "stdio",
"command": "npx",
"args": [
"playwright",
"run-test-mcp-server"
]
}