Handling Complex Page Elements
By Syncfusion®, Inc. · Last updated
Frames and nested frames, Shadow DOM and its limits, alert, confirm, prompt and beforeunload dialogs, unexpected overlays with addLocatorHandler, file uploads and file choosers, downloads, the keyboard and mouse APIs, drag and drop, working with lists, and the clipboard.
Handling Complex Page Elements
- Real pages are not just buttons and text boxes.
- They have payment forms inside frames, widgets inside Shadow DOM, pop-up dialogs, cookie banners, file uploads and downloads.
- Playwright has a tool for each. In this lesson, you will use them all on the Practice Shop's Widgets page.
Simple lesson: frames, dialogs, overlays, files and drag and drop each have their own Playwright tool.
A Real-World Example
- The Practice Shop takes card payments in a form from a payment company.
- That form sits in an iframe: a separate page inside the page.
- Normal locators cannot see inside it.
frameLocator()can.
What You Will Learn
- Frames and nested frames.
- Shadow DOM.
- Alert, confirm, prompt and beforeunload dialogs.
- Surprise overlays such as cookie banners.
- File uploads and downloads.
- The low-level keyboard and mouse.
- Drag and drop, lists, and the clipboard.
Frames
frameLocator()
- An iframe shows another page inside the current one. It has its own document.
page.frameLocator('iframe[name="payment"]')points into it. Then usegetByRole(),getByLabel()and the rest as usual.- You can also start from a locator:
page.getByTitle('Payment form').contentFrame().
Nested Frames
- A frame can contain another frame. Chain
frameLocator()to go deeper. - Since Playwright 1.63,
page.frameLocator()with no selector searches every frame on the page. It is handy when you do not care which frame holds the element.
page.frames()
page.frames()lists every frame, starting with the main one.page.frame({ name: 'secure' })finds one by name.
Animation: a locator steps from the main page into the payment frame, then into the nested 3-D Secure frame, to reach the one-time password box
Main page: /widgets
iframe name="payment"
iframe name="secure"
One-time password
page .frameLocator('iframe[name="payment"]') .frameLocator('iframe[name="secure"]').getByLabel('One-time password')
Each frameLocator() steps one frame deeper.
Simple lesson: frameLocator() steps into a frame. Chain it for frames inside frames.
Shadow DOM
How Locators Work with Shadow DOM
- Shadow DOM hides a widget's inner HTML from the rest of the page. Custom elements such as
<rating-widget>use it. - Playwright's locators go into open shadow roots by themselves. You write them as if the shadow root were not there.
Animation: getByRole button Rate 4 stars passes through the open shadow root of rating-widget and finds the button. The same kind of locator stops at the closed shadow root of secret-note and finds nothing
<rating-widget>
#shadow-root (open)
Rate 4 stars ✓ found
<secret-note>
#shadow-root (closed) 🔒 locators cannot enter ✘ not found
Locators pass through open shadow roots on their own. Closed shadow roots stay closed.
Limitations
- Closed shadow roots cannot be entered by locators.
- XPath does not enter shadow roots either. CSS and
getBy...locators do.
Simple lesson: locators enter open shadow roots for you. Closed ones and XPath stay outside.
Dialogs
Alert, Confirm and Prompt Dialogs
alert(),confirm()andprompt()are the browser's built-in pop-up boxes.- By default, Playwright dismisses them:
confirm()returns false,prompt()returns null.
page.on('dialog'), Accepting and Dismissing
- To answer a dialog, listen before the action that opens it:
page.once('dialog', (dialog) => dialog.accept());
dialog.accept('text')answers a prompt with text.dialog.dismiss()presses Cancel.dialog.type()is'alert','confirm','prompt'or'beforeunload'.dialog.message()is its text.
beforeunload Dialogs
- Some pages ask "Leave site?" when you close them. Playwright shows it only if you close with
page.close({ runBeforeUnload: true }), then handle it like any dialog.
If you add a dialog listener, it must accept or dismiss the dialog. A real page stalls while a dialog waits for an answer.
Simple lesson: listen with page.once('dialog') before the click, then accept() or dismiss().
Unexpected Overlays
addLocatorHandler()
- Cookie banners and newsletter pop-ups appear at random times and cover the page.
page.addLocatorHandler(locator, handler)says: "whenever this appears, run this handler, then carry on".- Playwright checks for it before each action and assertion.
Handling Cookie Banners and Popups
- Without the handlers, the clicks would wait, then time out with "intercepts pointer events".
Simple lesson: addLocatorHandler() closes surprise overlays for you, every time they appear.
File Uploads
setInputFiles()
locator.setInputFiles('tests/fixtures/avatar.png')chooses a file for an<input type="file">.- Paths are relative to where you run the tests (the project folder).
Uploading Multiple Files and Clearing
- Pass a list for multi-file inputs:
setInputFiles(['a.pdf', 'b.pdf']). - Pass a file made in memory:
{ name: 'note.txt', mimeType: 'text/plain', buffer: Buffer.from('hello') }. - Pass
[]to clear the selection.
File Chooser Events
- Some pages hide the file input behind a button. Wait for the file chooser instead:
Simple lesson: setInputFiles() uploads. Use the filechooser event when the input is hidden behind a button.
File Downloads
Waiting for the download Event
- Start waiting for the download before the click, like a popup:
const downloadPromise = page.waitForEvent('download');
await page.getByRole('link', { name: 'Export orders (CSV)' }).click();
const download = await downloadPromise;
suggestedFilename() and saveAs()
download.suggestedFilename()is the name the site proposed.download.saveAs(path)saves it where you want.download.path()is where Playwright saved it.
Verifying Downloaded Content
Simple lesson: wait for the download event, check the file name, then save and read the file.
Low-Level Keyboard and Mouse
page.keyboard
page.keyboardtypes into whatever has focus:press('Escape'),type('hello'),down('Shift')andup('Shift').
Keyboard Shortcuts and Modifier Keys
- Press combinations with
+:page.keyboard.press('Control+K'). ControlOrMeta+Kis Control on Windows and Linux, Command on macOS.
page.mouse and Mouse Wheel Scrolling
page.mouse.move(x, y),down(),up()andclick(x, y)work with screen positions.page.mouse.wheel(0, 600)scrolls down by 600 pixels.- Prefer locator actions. Use the mouse only for canvases, maps and drawing tools.
Simple lesson: page.keyboard types and presses shortcuts. page.mouse works by position.
Drag and Drop
dragTo()
source.dragTo(target)drags one element and drops it on another.
Manual Drag with the Mouse API
- For custom drag code, use the mouse: hover the source,
mouse.down(), hover the target,mouse.up().
// Manual drag, for custom drag-and-drop code
await page.getByText('Fix login bug').hover();
await page.mouse.down();
await page.getByLabel('Done').hover();
await page.mouse.up();
Simple lesson: dragTo() handles most drag and drop. The mouse API handles the rest.
Working with Lists
count() and all()
await locator.count()returns how many elements match. For checks, prefertoHaveCount().await locator.all()returns one locator per match, to loop over.
allTextContents()
allTextContents()andallInnerTexts()return the texts of all matches at once.
all() does not wait for the list to load. Assert first, for example with toHaveCount(), then loop.
Simple lesson: toHaveCount() to check, all() to loop, allTextContents() to read every text.
Clipboard (Advanced)
Reading and Writing the Clipboard
- Reading the clipboard needs permission:
context.grantPermissions(['clipboard-read', 'clipboard-write']). - Then the page, or
page.evaluate(), can usenavigator.clipboard.
Clipboard support differs between browsers. Keep clipboard tests in the projects that support them, and skip the rest with a condition on browserName.
Simple lesson: grant clipboard permissions first, then read or write the clipboard.
Try It in Your Project
- These steps take the lesson from the lab to your own app.
- List the iframes, dialogs and file uploads in your app. Each needs its own pattern.
- Add
addLocatorHandler()for your cookie banner or promo popup. - Write one download test that checks the file's content, not just its name.
Simple lesson: use one idea from this lesson on your own app this week.
TL;DR
frameLocator()goes into frames. Chain it for nested frames.- Locators enter open shadow roots, not closed ones, and XPath never does.
page.once('dialog')before the click. Thenaccept()ordismiss().addLocatorHandler()closes surprise overlays.setInputFiles()uploads.waitForEvent('download')catches downloads.page.keyboard,page.mouse,dragTo(),all()and the clipboard cover the rest.
Common mistakes
| Mistake | What happens | Fix |
|---|---|---|
page.getByLabel() for a field inside an iframe | Nothing is found | Use page.frameLocator(...).getByLabel() |
| Adding the dialog listener after the click | The dialog is already dismissed | Add page.once('dialog') first |
| A dialog listener that never answers | A real page stalls | Always accept() or dismiss() |
click({ force: true }) through a cookie banner | The banner gets the click | Use addLocatorHandler() or close the banner |
| Waiting for the download after the click | The event can be missed | Start waitForEvent('download') first |
Check Yourself
Exercises
What to Remember
- Frames:
frameLocator(). Shadow DOM: locators go in by themselves, if it is open. - Dialogs: listen first, then answer.
- Overlays:
addLocatorHandler(). - Files:
setInputFiles()up,waitForEvent('download')down. - Keyboard, mouse,
dragTo(),all()and the clipboard handle the rest.
Next: Authentication and Storage State