Join Andrey Lushnikov, Principal Engineer at Microsoft, as he shares insights into the decisions behind the creation and development Playwright; how Playwright is the only tool that covers modern automation needs; and why it is believed Playwright is the first framework that can be used for cross-browser testing.
14. import {chromium, firefox, webkit} from 'playwright';
for (const browserType of [chromium, firefox, webkit]) {
const browser = await browserType.launch();
const page = await browser.newPage();
await page.goto('https://playwright.dev');
await page.screenshot({path: `image-${browserType.name()}.png`});
await browser.close();
}
J
a
v
a
S
c
r
i
p
t
15. from playwright.sync_api import sync_playwright
with sync_playwright() as p:
for browser_type in [p.chromium, p.firefox, p.webkit]:
browser = browser_type.launch()
page = browser.new_page()
page.goto("https://playwright.dev")
page.screenshot(path="image-" + browser_type.name + ".png")
browser.close()
P
y
t
h
o
n
24. - Chromium Development
- Improving Chrome DevTools Protocol
- Google Chrome, Microsoft Edge, Opera, etc
Level 1: Browser Engineering
C++
25. - Chromium Development
- Improving Chrome DevTools Protocol
- Google Chrome, Microsoft Edge, Opera, etc
- WebKit Development
- Improving Web Inspector Protocol
- Safari, Mobile Safari, Epiphany, etc
Level 1: Browser Engineering
C++
26. - Chromium Development
- Improving Chrome DevTools Protocol
- Google Chrome, Microsoft Edge, Opera, etc
- WebKit Development
- Improving Web Inspector Protocol
- Safari, Mobile Safari, Epiphany, etc
- Firefox Development
- Improving “Juggler” Protocol
- Mozilla Firefox
Level 1: Browser Engineering
C++
27. - Chromium Development
- Improving Chrome DevTools Protocol
- Google Chrome, Microsoft Edge, Opera, etc
- WebKit Development
- Improving Web Inspector Protocol
- Safari, Mobile Safari, Epiphany, etc
- Firefox Development
- Improving “Juggler” Protocol
- Mozilla Firefox
Level 1: Browser Engineering
C++
29. TypeScript
- Single web automation protocol
- Unify all remote debugging protocols
- Expose “Downloads API” in driver
Level 2: Playwright Driver
🎭 Playwright Driver
Level 1: Browser Engineering
C++
30. TypeScript
Level 3: Language Bindings
🎭 Playwright Driver
Level 1: Browser Engineering
C++
Level 2: Playwright Driver
31. TypeScript
Level 3: Language Bindings
🎭 Playwright Driver
Level 1: Browser Engineering
C++
Level 2: Playwright Driver
Playwright
Playwright
for
Java
Playwright
for
Python
Playwright
for
C#
Expose language idiomatic API to
control downloads
32. TypeScript
Level 3: Language Bindings
🎭 Playwright Driver
Level 1: Browser Engineering
C++
Level 2: Playwright Driver
Playwright
Playwright
for
Java
Playwright
for
Python
Playwright
for
C#
Expose language idiomatic API to
control downloads
33. TypeScript
Level 3: Language Bindings
🎭 Playwright Driver
Level 1: Browser Engineering
C++
Level 2: Playwright Driver
Playwright
Playwright
for
Java
Playwright
for
Python
Playwright
for
C#
Expose language idiomatic API to
control downloads
34. TypeScript
Being Dependable
🎭 Playwright Driver
C++
Playwright
Playwright
for
Java
Playwright
for
Python
Playwright
for
C#
Cross-Browser Testing Tool
● 👀 8 teams from multiple companies
● 🐌 Slow feedback loop (months /
years)
Playwright
● 💫 1 team from a single company
● 🐆 Fast feedback loop (days / weeks)
35. More Like This
● Downloads API
● Screencast API
● Drag & Drop API
● Clipboard API
● Browser-Level cookies
● Browser Contexts
● Per-context HTTP proxy
● ...
36. Summary: Playwright is Dependable
👌 We Triage Issues in <48 hours
📚 We Fix Issues (>1600 fixed)
✈ We Take Full Responsibility
59. await page.goto('https://form.example.com');
// Shall we wait for fields to be enabled?..
await page.fill('#email', 'aslushnikov@gmail.com');
await page.fill('#password', 'mypassword');
// Wait for button to get enabled and click submit
setTimeout(async () => {
await page.click('#submit');
}, 1000);
N
o
n
-
P
l
a
y
w
r
i
g
h
t
P
s
e
u
d
o
-
C
o
d
e
60. await page.goto('https://form.example.com');
// Shall we wait for fields to be enabled?..
await page.fill('#email', 'aslushnikov@gmail.com');
await page.fill('#password', 'mypassword');
// Wait for button to get enabled and click submit
setTimeout(async () => {
await page.click('#submit');
}, 1000);
N
o
n
-
P
l
a
y
w
r
i
g
h
t
P
s
e
u
d
o
-
C
o
d
e
61. await page.goto('https://form.example.com');
// Shall we wait for fields to be enabled?..
await page.fill('#email', 'aslushnikov@gmail.com');
await page.fill('#password', 'mypassword');
// Wait for button to get enabled and click submit
setTimeout(async () => {
await page.click('#submit');
}, 1000);
N
o
n
-
P
l
a
y
w
r
i
g
h
t
P
s
e
u
d
o
-
C
o
d
e
Time Does Not Exist in the Cloud!
66. // ✨ Playwright auto-waiting by default!
await page.goto('https://form.example.com');
await page.fill('#email', 'aslushnikov@gmail.com');
await page.fill('#password', 'mypassword');
await page.click('#submit');
P
l
a
y
w
r
i
g
h
t
67. // ✨ Playwright auto-waiting by default!
await page.goto('https://form.example.com');
await page.fill('#email', 'aslushnikov@gmail.com');
await page.fill('#password', 'mypassword');
await page.click('#submit');
P
l
a
y
w
r
i
g
h
t
68. // ✨ Playwright auto-waiting by default!
await page.goto('https://form.example.com');
await page.fill('#email', 'aslushnikov@gmail.com');
await page.fill('#password', 'mypassword');
await page.click('#submit');
P
l
a
y
w
r
i
g
h
t
69. // ✨ Playwright auto-waiting by default!
await page.goto('https://form.example.com');
await page.fill('#email', 'aslushnikov@gmail.com');
await page.fill('#password', 'mypassword');
await page.click('#submit');
P
l
a
y
w
r
i
g
h
t
79. import {webkit, devices} from 'playwright';
const browser = await webkit.launch();
const context = await browser.newContext({
...devices['iPhone 12 Pro'],
});
const page = await context.newPage();
await page.goto('https://playwright.dev');
await browser.close();
📱
D
e
v
i
c
e
E
m
u
l
a
t
i
o
n
80. import {webkit, devices} from 'playwright';
const browser = await webkit.launch();
const context = await browser.newContext({
...devices['iPhone 12 Pro'],
});
const page = await context.newPage();
await page.goto('https://playwright.dev');
await browser.close();
📱
D
e
v
i
c
e
E
m
u
l
a
t
i
o
n
81. import {webkit, devices} from 'playwright';
const browser = await webkit.launch();
const context = await browser.newContext({
...devices['iPhone 12 Pro'],
});
const page = await context.newPage();
await page.goto('https://playwright.dev');
await browser.close();
📱
D
e
v
i
c
e
E
m
u
l
a
t
i
o
n
82. import {chromium, firefox, webkit} from 'playwright';
const browser = await firefox.launch();
const context = await browser.newContext({
geolocation: { longitude: 48.858455, latitude: 2.294474 },
permissions: ['geolocation']
});
const page = await context.newPage();
await page.goto('https://playwright.dev');
await context.setGeolocation({
longitude: 29.97, latitude: 31.13
});
G
e
o
l
o
c
a
t
i
o
n
&
P
e
r
m
i
s
s
i
o
n
s
83. import {chromium, firefox, webkit} from 'playwright';
const browser = await firefox.launch();
const context = await browser.newContext({
geolocation: { longitude: 48.858455, latitude: 2.294474 },
permissions: ['geolocation']
});
const page = await context.newPage();
await page.goto('https://playwright.dev');
await context.setGeolocation({
longitude: 29.97, latitude: 31.13
});
G
e
o
l
o
c
a
t
i
o
n
&
P
e
r
m
i
s
s
i
o
n
s
84. import {chromium, firefox, webkit} from 'playwright';
const browser = await firefox.launch();
const context = await browser.newContext({
geolocation: { longitude: 48.858455, latitude: 2.294474 },
permissions: ['geolocation']
});
const page = await context.newPage();
await page.goto('https://playwright.dev');
await context.setGeolocation({
longitude: 29.97, latitude: 31.13
});
G
e
o
l
o
c
a
t
i
o
n
&
P
e
r
m
i
s
s
i
o
n
s
85. import {chromium, firefox, webkit} from 'playwright';
const browser = await firefox.launch();
const context = await browser.newContext({
geolocation: { longitude: 48.858455, latitude: 2.294474 },
permissions: ['geolocation']
});
const page = await context.newPage();
await page.goto('https://playwright.dev');
await context.setGeolocation({
longitude: 29.97, latitude: 31.13
});
G
e
o
l
o
c
a
t
i
o
n
&
P
e
r
m
i
s
s
i
o
n
s
86. import {webkit, chromium, firefox} from 'playwright';
const browser = await chromium.launch();
const context = await browser.newContext({
recordVideo: {
dir: 'videos/',
size: { width: 800, height: 600 },
},
});
const page = await context.newPage();
await page.goto('https://playwright.dev');
await page.close();
🎥
V
i
d
e
o
s
87. import {webkit, chromium, firefox} from 'playwright';
const browser = await chromium.launch();
const context = await browser.newContext({
recordVideo: {
dir: 'videos/',
size: { width: 800, height: 600 },
},
});
const page = await context.newPage();
await page.goto('https://playwright.dev');
await page.close();
🎥
V
i
d
e
o
s
88. import {webkit, chromium, firefox} from 'playwright';
const browser = await chromium.launch();
const context = await browser.newContext({
recordVideo: {
dir: 'videos/',
size: { width: 800, height: 600 },
},
});
const page = await context.newPage();
await page.goto('https://playwright.dev');
await page.close();
🎥
V
i
d
e
o
s
89. import {webkit, chromium, firefox} from 'playwright';
const browser = await webkit.launch();
const page = await browser.newPage();
page.on('request', r => console.log(r.method(), r.url()));
page.on('websocket', ws => console.log(ws.url()));
await page.goto('https://playwright.dev');
await browser.close();
🌎
N
e
t
w
o
r
k
&
W
e
b
S
o
c
k
e
t
s
90. import {webkit, chromium, firefox} from 'playwright';
const browser = await webkit.launch();
const page = await browser.newPage();
page.on('request', r => console.log(r.method(), r.url()));
page.on('websocket', ws => console.log(ws.url()));
await page.goto('https://playwright.dev');
await browser.close();
🌎
N
e
t
w
o
r
k
&
W
e
b
S
o
c
k
e
t
s
91. import {webkit, chromium, firefox} from 'playwright';
const browser = await firefox.launch();
const page = await browser.newPage();
await page.route('**/*.{png,jpg,jpeg}', route => route.abort());
await page.goto('https://playwright.dev');
await browser.close();
P
a
g
e
R
e
q
u
e
s
t
I
n
t
e
r
c
e
p
t
i
o
n
92. import {webkit, chromium, firefox} from 'playwright';
const browser = await firefox.launch();
const context = await browser.newContext();
await context.route('**/*.{png,jpg,jpeg}', route => route.abort());
const page = await page.newPage();
await page.goto('https://playwright.dev');
await browser.close();
C
o
n
t
e
x
t
R
e
q
u
e
s
t
I
n
t
e
r
c
e
p
t
i
o
n
93. import {webkit, chromium, firefox} from 'playwright';
const browser = await webkit.launch();
const page = await browser.newPage();
await page.goto('https://playwright.dev');
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('button#delayed-download')
]);
console.log(await download.path());
await browser.close();
D
o
w
n
l
o
a
d
s
94. import {webkit, chromium, firefox} from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://playwright.dev');
await page.click('button'); // CSS selector
await page.click('xpath=//button'); // XPath selector
await page.click('text=Log in'); // Text selector
await page.click(':nth-match(:text("Buy"), 3)'); // N-th match
await page.click('div:right-of(:text("Name"))'); // Layout 😱
await page.click(`xpath=//form >> text=Submit`); // Composite
S
m
a
r
t
S
e
l
e
c
t
o
r
s
95. import {webkit, chromium, firefox} from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://playwright.dev');
await page.click('button'); // CSS selector
await page.click('xpath=//button'); // XPath selector
await page.click('text=Log in'); // Text selector
await page.click(':nth-match(:text("Buy"), 3)'); // N-th match
await page.click('div:right-of(:text("Name"))'); // Layout 😱
await page.click(`xpath=//form >> text=Submit`); // Composite
S
m
a
r
t
S
e
l
e
c
t
o
r
s
96. import {webkit, chromium, firefox} from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://playwright.dev');
await page.click('button'); // CSS selector
await page.click('xpath=//button'); // XPath selector
await page.click('text=Log in'); // Text selector
await page.click(':nth-match(:text("Buy"), 3)'); // N-th match
await page.click('div:right-of(:text("Name"))'); // Layout 😱
await page.click(`xpath=//form >> text=Submit`); // Composite
S
m
a
r
t
S
e
l
e
c
t
o
r
s
97. import {webkit, chromium, firefox} from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://playwright.dev');
await page.click('button'); // CSS selector
await page.click('xpath=//button'); // XPath selector
await page.click('text=Log in'); // Text selector
await page.click(':nth-match(:text("Buy"), 3)'); // N-th match
await page.click('div:right-of(:text("Name"))'); // Layout 😱
await page.click(`xpath=//form >> text=Submit`); // Composite
S
m
a
r
t
S
e
l
e
c
t
o
r
s
98. import {webkit, chromium, firefox} from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://playwright.dev');
await page.click('button'); // CSS selector
await page.click('xpath=//button'); // XPath selector
await page.click('text=Log in'); // Text selector
await page.click(':nth-match(:text("Buy"), 3)'); // N-th match
await page.click('div:right-of(:text("Name"))'); // Layout 😱
await page.click(`xpath=//form >> text=Submit`); // Composite
S
m
a
r
t
S
e
l
e
c
t
o
r
s
99. import {webkit, chromium, firefox} from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://playwright.dev');
await page.click('button'); // CSS selector
await page.click('xpath=//button'); // XPath selector
await page.click('text=Log in'); // Text selector
await page.click(':nth-match(:text("Buy"), 3)'); // N-th match
await page.click('div:right-of(:text("Name"))'); // Layout 😱
await page.click(`xpath=//form >> text=Submit`); // Composite
S
m
a
r
t
S
e
l
e
c
t
o
r
s
100. import {webkit, chromium, firefox} from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://playwright.dev');
await page.click('button'); // CSS selector
await page.click('xpath=//button'); // XPath selector
await page.click('text=Log in'); // Text selector
await page.click(':nth-match(:text("Buy"), 3)'); // N-th match
await page.click('div:right-of(:text("Name"))'); // Layout 😱
await page.click(`xpath=//form >> text=Submit`); // Composite
S
m
a
r
t
S
e
l
e
c
t
o
r
s
101. // text content
const content = await page.textContent('nav:first-child');
expect(content).toBe('home');
// Attributes
const alt = await page.getAttribute('input', 'alt');
expect(alt).toBe('Text');
// Checkbox state
const checked = await page.isChecked('input');
expect(checked).toBeTruthy();
// Visibility
const visible = await page.isVisible('input');
expect(visible).toBeTruthy();
A
s
s
e
r
t
i
o
n
s
102. // text content
const content = await page.textContent('nav:first-child');
expect(content).toBe('home');
// Attributes
const alt = await page.getAttribute('input', 'alt');
expect(alt).toBe('Text');
// Checkbox state
const checked = await page.isChecked('input');
expect(checked).toBeTruthy();
// Visibility
const visible = await page.isVisible('input');
expect(visible).toBeTruthy();
A
s
s
e
r
t
i
o
n
s
103. // text content
const content = await page.textContent('nav:first-child');
expect(content).toBe('home');
// Attributes
const alt = await page.getAttribute('input', 'alt');
expect(alt).toBe('Text');
// Checkbox state
const checked = await page.isChecked('input');
expect(checked).toBeTruthy();
// Visibility
const visible = await page.isVisible('input');
expect(visible).toBeTruthy();
A
s
s
e
r
t
i
o
n
s
104. // text content
const content = await page.textContent('nav:first-child');
expect(content).toBe('home');
// Attributes
const alt = await page.getAttribute('input', 'alt');
expect(alt).toBe('Text');
// Checkbox state
const checked = await page.isChecked('input');
expect(checked).toBeTruthy();
// Visibility
const visible = await page.isVisible('input');
expect(visible).toBeTruthy();
A
s
s
e
r
t
i
o
n
s
105. import {chromium} from 'playwright';
const browser = await chromium.launch({
// Can be 'chrome', 'chrome-beta', 'msedge-beta', 'msedge-dev'
channel: 'msedge',
});
const page = await context.newPage();
await page.goto('https://playwright.dev');
await browser.close();
M
i
c
r
o
s
o
f
t
E
d
g
e
106. import {chromium} from 'playwright';
const browser = await chromium.launch({
// Can be 'chrome', 'chrome-beta', 'msedge-beta', 'msedge-dev'
channel: 'msedge',
});
const page = await context.newPage();
await page.goto('https://playwright.dev');
await browser.close();
M
i
c
r
o
s
o
f
t
E
d
g
e
107. More Features
● Built-in Shadow DOM piercing
● Idiomatic frames API
● Custom Selector Engines
● TimeZone / Locale / Color Scheme emulation
● Per-Context HTTP Proxy
● Workers / Service Workers
● Console sniffing
● Dialogs
● File Uploads
● Element Handles
● ...
108. ● ubuntu-18.04 (chromium)
● ubuntu-18.04 (firefox)
● ubuntu-18.04 (webkit)
● ubuntu-20.04 (chromium)
● ubuntu-20.04 (firefox)
● ubuntu-20.04 (webkit)
● macos-10.14 (chromium)
● macos-10.14 (firefox)
● macos-10.14 (webkit)
● macos-10.15 (chromium)
● macos-10.15 (firefox)
● macos-10.15 (webkit)
● macos-11.0 (chromium)
● macos-11.0 (firefox)
● macos-11.0 (webkit)
● macos-11.0 arm64 (chromium)
● macos-11.0 arm64 (firefox)
● macos-11.0 arm64 (webkit)
Rigorous Testing ● Windows (chromium)
● Windows (firefox)
● Windows (webkit)
● test-package-installations (^10.17.0)
● test-package-installations (^12.0.0)
● test-package-installations (^14.1.0)
● Headful Linux (chromium)
● Headful Linux (firefox)
● Headful Linux (webkit)
● Transport (driver)
● Transport (service)
● Video Linux (chromium)
● Video Linux (firefox)
● Video Linux (webkit)
● Android Emulator (shard 1)
● Android Emulator (shard 2)
● Chrome Stable (Linux)
● Chrome Stable (Win)
● Chrome Stable (Mac)
● Edge Stable (Win)
● Electron Linux
113. ● All Browsers
○ Chrome, Safari, Firefox
🎭 Playwright is Ubiquitous
114. ● All Browsers
○ Chrome, Safari, Firefox
● All OS
○ Linux, Mac, Windows
🎭 Playwright is Ubiquitous
115. 🎭 Playwright is Ubiquitous
● All Browsers
○ Chrome, Safari, Firefox
● All OS
○ Linux, Mac, Windows
● All Popular Languages
○ JavaScript / TypeScript
○ Java
○ Python
○ C# (alpha)
116. CI/CD, Services, Clients
● Any CI/CD
○ Github Actions, Travis CI, Azure Pipelines, Jenkins, Circle CI,
AWS, GCP, …
131. Lively 🐈
● Active Development
○ 2500+ commits in the last year
○ 1600+ closed issues
● Growing Team
○ We’re hiring!
● Expanding Horizons
132. const { _electron } = require('playwright');
(async () => {
const electronApp = await _electron.launch({ args: ['main.js'] });
const window = await electronApp.firstWindow();
console.log(await window.title());
await window.screenshot({ path: 'intro.png' });
await window.click('text=Click me');
await electronApp.close();
})();
🧪
E
x
p
e
r
i
m
e
n
t
a
l
:
E
l
e
c
t
r
o
n
.
j
s
133. const { _android } = require('playwright');
(async () => {
const [device] = await _android.devices();
await device.shell('am force-stop com.android.chrome');
const context = await device.launchBrowser();
const page = await context.newPage();
await page.goto('https://playwright.dev/');
await page.screenshot({ path: 'page.png' });
await context.close();
await device.close();
})();
🧪
E
x
p
e
r
i
m
e
n
t
a
l
:
A
n
d
r
o
i
d
142. How-To: Authentication Re-use
Context 1 Context N
Storage State
2. Extract & Save Storage State
3. Re-Use Storage State
1. npx playwright codegen --save-storage=auth.json