SlideShare a Scribd company logo
@giltayar
You’re almost there:
turbocharge your functional tests
with visual powers
Gil Tayar (@giltayar)
August 2018
This presentation: http://bit.ly/turbocharge-webinar
Github repo: https://github.com/giltayar/realworld-frontend-testing
@giltayar@giltayar
About Me ● My developer experience goes all the way
back to the ‘80s.
● Am, was, and always will be a developer
● Testing the code I write is my passion
● Currently evangelist and architect @
Applitools
● We deliver Visual Testing tools:
If you’re serious about testing, checkout
Applitools Eyes
● Sometimes my arms bend back
● But the gum I like is coming back in style
@giltayar
@giltayar
What Are Functional
Tests?
@giltayar
So many kinds of tests...
@giltayar
Tests, Tests, Glorious Tests!
● Unit
● Integration
● E2E
● Acceptance
● Visual
● Contract
● Browser
● Automation
● Functional
● And the list goes on…
@giltayar
Not to mention the
different interpretations
@giltayar
Functional Tests
@giltayar
Functional tests are
automation tests that automate the browser
in order to test the functionality
of a web application*
* Or a mobile app
@giltayar
Functional tests are
automation tests that automate the browser
in order to test the functionality
of a web application*
* Or a mobile app
@giltayar
Let’s write a Functional Test
@giltayar
● Huge library (npm)
○ Biggest.
○ Very high quality
○ Lots of tests!
● DLL hell is gone (almost…)
● No bullshit libraries (readme-s,
not javadocs)
I love NodeJS
● Constantly improving in
performance
● Fast startup times
● ES2018 is a wonderful language
● Async programming is
wonderful
● Good FP language too
● Constantly evolving
@giltayar
● All the cool new automation frameworks are in
NodeJS/JavaScript
● It is the language of the frontend developers
○ Writing tests with the frontend developers
○ Code sharing!
I love NodeJS
@giltayar
Let’s give a look at the app we’re testing
@giltayar
Let’s write a Functional Test in JavaScript!
https://github.com/giltayar/realworld-frontend-testing, branch step1
@giltayar
All Functional Tests are Stories
@giltayar
All Functional Tests Look Like This
● Action
● Action
● Validation
● Action
● Validation
● Action
● Action
● Action
● Validation
@giltayar
Let’s do the following functional story
https://github.com/giltayar/realworld-frontend-testing, branch step2
@giltayar
async function waitFor(selector) {
await driver.wait(
until.elementLocated(By.css(selector)))
}
async function setText(selector, text) {
const field = await driver.findElement(
By.css(selector))
await field.sendKeys(text)
}
Create some utility functions
async function click(selector) {
const element = await driver.findElement(
By.css(selector))
await element.click()
}
async function getText(selector) {
const element = await driver.findElement(
By.css(selector))
return await element.getText()
}
@giltayar
Register the User
action
action
action
action
@giltayar
// action: browse to registration page
await driver.get('http://localhost:3000/register' )
// action: set username, password, email to something
await setText('input[placeholder=Username]' , 'ausername')
await setText('input[placeholder=Password]' , 'aPassword')
await setText('input[placeholder=Email]' , 'an@email.com' )
// action: submit form
await click('button[type=submit]' )
Register the User
@giltayar
// action: browse to registration page
await driver.get('http://localhost:3000/register' )
// action: set username, password, email to something
await setText('input[placeholder=Username]' , 'ausername')
await setText('input[placeholder=Password]' , 'aPassword')
await setText('input[placeholder=Email]' , 'an@email.com' )
// action: submit form
await click('button[type=submit]' )
Fragile Selector Problem
Fragile selectors
@giltayar
class RegisterPage {
async function registerUser(username, email, password) {
// ...
}
}
Example RegisterPage Object
@giltayar
Empty Home Page
validation
validation
action validation
@giltayar
await waitFor('img[alt=ausername]')
// validate username
expect(await getText('a[href="/@ausername"]')).to.equal('ausername')
// validate articles list
expect(await getText('.article-preview'))
.to.equal('No articles are here... yet.')
// validate active tab
expect(await getText('.nav-link.active')).to.equal('Your Feed')
Validate the Empty User Home Page
@giltayar
Publish an article
action
action
action
action
action
@giltayar
// action: click on new post
const newPost = await driver.findElement (By.partialLinkText ('New Post'))
await newPost.click()
await waitFor('input[placeholder="Article Title"]' )
// action: set the title, description, and article
await setText('input[placeholder="Article Title"]' , 'a title')
await setText('input[placeholder="What's this article about?"]' , 'something' )
await setText('textarea[placeholder*="Write your article"]' , 'wonderful' )
// action: set the tags
const tags = await driver.findElement (By.css('input[placeholder="Enter tags"]' ))
for (const tag of ['a', 'b', 'c']) await tags.sendKeys(tag, Key.ENTER)
// action: submit form
await click('button[type=button]' )
Publish a Post
@giltayar
See that it’s there, and add a comment
validation
validation
validation
validation
action
action
@giltayar
await waitFor('h1')
// validate title
expect(await getText('h1')).to.equal('a title')
// validate article content
expect(await getText('.article-content')).to.include('wonderful')
// validate tags
expect(await getText('.tag-list')).to.equal('abc')
Validate Post
@giltayar
// action: set comment
await setText('textarea', 'a comment')
// action: submit comment
await click('button[type=submit]')
Add Comment
@giltayar
Validate the comment
validation
validation
@giltayar
await waitFor('div.card .card-block')
// validate comment text
expect(await getText('div.card .card-block')).to.equal('a comment')
// validate comment username
expect(await getText('div.card a.comment-author:nth-child(2)'))
.to.equal('ausername')
Validate Comment
@giltayar
Logout
action
@giltayar
// action: goto settings page
await driver.get('http://localhost:3000/settings')
await waitFor('button.btn-outline-danger')
// action: click logout button
await click('button.btn-outline-danger')
Logout
@giltayar
Check Blog with anonymous user
validation
validation
validation
validation
validation
@giltayar
// goto home page
await driver.get('http://localhost:3000/')
// validate post author
expect(await getText('a.author'))
.to.equal('ausername')
// validate post title
expect(await getText('.article-preview h1'))
.to.equal('a title')
// validate post description
expect(await getText('.article-preview h1 + p'))
.to.equal('something')
// validate post tags
expect(await getText('.article-preview ul'))
.to.equal('abc')
Validate Home Page for Anonymous User
// validate popular tags
expect(await getText('.sidebar .tag-list'))
.to.equal('abc')
// action: goto post page
await click('.article-preview h1')
await waitFor('.article-page')
// validate post title
expect(await getText('h1')).to.equal('a title')
// validate article description
expect(await getText('div.article-content p'))
.to.equal('wonderful')
// validate article tags
expect(await getText('ul.tag-list')).to.equal('abc')
@giltayar
Problems with Actions
● Fragile Selectors
● Complex Code
● Solution: Page Objects
@giltayar
Problems with Validations
● Fragile Selectors
● Complex Code
● Too much to validate
● Only positive check
○ We don’t validate what we don’t know about
● Solution: Page Objects, partially
@giltayar
Actions are Focused
Validations are Holistic
@giltayar
Let’s see why validations are holistic
● Add the following to Register.js:
onSubmit: (username, email, password) => {
dispatch({ type: UPDATE_FIELD_AUTH, key: 'username', value: '' })
const payload = agent.Auth.register(username, email, password);
● This will clear the username after every submit
@giltayar
Let’s see what the effect of this bug will be
@giltayar
But does the test pass?
Yes, it does!
@giltayar
Actions are Focused
Validations are should be Holistic
@giltayar
Let’s revert the “bug”, rebuild,
and figure out how to solve the validation
problem
@giltayar
Solving the Validation Problem
@giltayar
What else is holistic?
@giltayar
Vision!
@giltayar
Replace Humans with Image Diffing!
@giltayar
Let’s do some live coding for that!
https://github.com/giltayar/realworld-frontend-testing, branch step3
@giltayar
So we’ve replace this...
expect(await getText('.error-messages' )).to.include("email can't be
blank")
With this…
expect(Buffer.from(await driver.takeScreenshot (),
'base64')).to.matchImage(
'registration-blank-email-error' ,
)
@giltayar
Why is this better?
It’s just one line replacing another
@giltayar
Solutions to problems with validations
● Fragile Selectors
○ No more selectors
● Complex Code
○ Same code for all validations
● Too much to validate
○ We validate holistically
● Only positive check
○ We validate things we didn’t even think of validating
@giltayar
Solutions to problems with validations
● Fragile Selectors
○ No more selectors
● Complex Code
○ Same code for all validations
● Too much to validate
○ We validate holistically
● Only positive check
○ We validate things we didn’t even think of validating
@giltayar
Let’s try it out...
● Run the tests without the bug
● Copy baseline image to expected-screenshots folder
● Add the following to Register.js:
onSubmit: (username, email, password) => {
dispatch({ type: UPDATE_FIELD_AUTH, key: 'username', value: '' })
const payload = agent.Auth.register(username, email, password);
● This will clear the username after every submit
@giltayar
Functional vs Visual Validations
● Let’s test it in step2: functional validations
git checkout step2 && npm run build && npm test
● The test passes! (and it shouldn’t)
● Now let’s try step3: visual validations
git checkout step2 && npm test
● The test fails! (as it should)
@giltayar
Let’s revert the “bug”, rebuild,
and continue looking at
visual vs. functional valiation
@giltayar
Functional Validations are Focused
Visual Validations are Holistic
@giltayar
async function validatePost() {
await waitFor('.validate-content')
// validate title
expect(await getText('h1')).to.equal('a title')
// validate article content
expect(await getText('.article-content')).to.include('wonderful')
// validate tags
expect(await getText('.tag-list')).to.equal('abc')
}
And we’ve change this...
@giltayar
async function validatePost() {
await waitFor('.article-content')
await checkWindow('new-post')
}
Into this...
@giltayar
// goto home page
await driver.get('http://localhost:3000/')
// validate post author
expect(await getText('a.author'))
.to.equal('ausername')
// validate post title
expect(await getText('.article-preview h1'))
.to.equal('a title')
// validate post description
expect(await getText('.article-preview h1 + p'))
.to.equal('something')
// validate post tags
expect(await getText('.article-preview ul'))
.to.equal('abc')
And this long code...
// validate popular tags
expect(await getText('.sidebar .tag-list'))
.to.equal('abc')
// action: goto post page
await click('.article-preview h1')
await waitFor('.article-page')
// validate post title
expect(await getText('h1')).to.equal('a title')
// validate article description
expect(await getText('div.article-content p'))
.to.equal('wonderful')
// validate article tags
expect(await getText('ul.tag-list')).to.equal('abc')
@giltayar
async function validateBlog() {
await driver.get('http://localhost:3000/')
await checkWindow('anonymous-home-page')
await click('.article-preview h1')
await waitFor('.article-page')
await checkWindow('anonymous-blog-post-view')
}
Into this...
@giltayar
Solutions to problems with validations
● Fragile Selectors
○ No more selectors
● Complex Code
○ Same code for all validations
● Too much to validate
○ We validate holistically
● Only positive check
○ We validate things we didn’t even think of validating
@giltayar
Problems with Naive Image Diffing
● Small anti-aliasing differences
○ Mac vs. Linux vs. Windows
○ Different GPUs
● Date/time problems
○ regions that are different from run to run
● Full screen vs. Viewport
● Comparing is a pain
● “Accepting” a new baseline is a pain
@giltayar
Problems with Naive Image Diffing
● Small anti-aliasing differences
○ Mac vs. Linux vs. Windows
○ Different GPUs
● Date/time problems
○ regions that are different from run to run
● Full screen vs. Viewport
● Comparing is a pain
● “Accepting” a new baseline is a pain
@giltayar
Cloud-Based Solutions
@giltayar
Cloud-based solution
● Visual diffing algorithms see like a human sees
○ Ignoring the small differences
○ AI-level algorithms are now involved and keep getting better
● Screenshot management tools are available, enabling you to
○ See the diffs
○ Approve new baselines
○ Open bugs on diffs that are bugs
@giltayar
Let’s Run the Demo
https://github.com/giltayar/realworld-frontend-testing, branch step4
@giltayar
Let’s change some code
to see some of the
baseline management tooling
@giltayar
Supercharging your
functional tests
@giltayar
Supercharging Your Test with Visual Validations
● Write code that issues action after action, to create a long story
○ You can use a recorder like Selenium IDE or one of the new cloud-based
ones
● Intersperse your code with visual validations
○ You can use the open-source solutions or a cloud-based option
● You get two for the price of one:
○ Much simpler functional validations
○ Visual validation of your page: catch those visual bugs that you couldn’t
catch till now!
@giltayar
Thank You
Gil Tayar (@giltayar)
This presentation: http://bit.ly/turbocharge-webinar
The Github repo: https://github.com/giltayar/realworld-frontend-testing

More Related Content

PDF
An introduction to Angular2
PDF
Commit University - Exploring Angular 2
PDF
Testing React hooks with the new act function
PDF
Angular Application Testing
PPTX
SOLID in the Wild: Life when your software is actually soft
PDF
Are app servers still fascinating
PDF
Kiss PageObjects [01-2017]
PDF
Dagger 2, 2 years later
An introduction to Angular2
Commit University - Exploring Angular 2
Testing React hooks with the new act function
Angular Application Testing
SOLID in the Wild: Life when your software is actually soft
Are app servers still fascinating
Kiss PageObjects [01-2017]
Dagger 2, 2 years later

What's hot (20)

PDF
Angular 2 - The Next Framework
PDF
Top100summit 谷歌-scott-improve your automated web application testing
PDF
Angular2 workshop
PDF
Exploring Angular 2 - Episode 2
PDF
Introduction to Angular 2
PPTX
Introduction to Google Guice
PDF
Angular2 with TypeScript
KEY
Ruby On Rails Pitfalls
PPTX
Neoito — Design patterns and depenedency injection
PDF
Polyglot automation - QA Fest - 2015
PDF
Angular Dependency Injection
PDF
Intro to Unit Testing in AngularJS
PDF
Easy tests with Selenide and Easyb
PPT
Google Guice
PDF
Design Patterns in XCUITest
ODP
Android training day 4
PDF
Understanding Angular 2 - Shmuela Jacobs - Codemotion Milan 2016
PDF
Solid angular
ODP
ODP
Angular js-crash-course
Angular 2 - The Next Framework
Top100summit 谷歌-scott-improve your automated web application testing
Angular2 workshop
Exploring Angular 2 - Episode 2
Introduction to Angular 2
Introduction to Google Guice
Angular2 with TypeScript
Ruby On Rails Pitfalls
Neoito — Design patterns and depenedency injection
Polyglot automation - QA Fest - 2015
Angular Dependency Injection
Intro to Unit Testing in AngularJS
Easy tests with Selenide and Easyb
Google Guice
Design Patterns in XCUITest
Android training day 4
Understanding Angular 2 - Shmuela Jacobs - Codemotion Milan 2016
Solid angular
Angular js-crash-course
Ad

Similar to Visual Testing: Turbo-Charge Your Functional Tests with Visual Powers in Just 8 Lines of Code -- by Gil Tayar (20)

PPTX
Visual Testing: The Missing Piece of the Puzzle -- presentation by Gil Tayar
PDF
Mastering UI automation at Scale: Key Lessons and Best Practices (By Fernando...
PDF
Functional Testing - A Detailed Guide.pdf
PPTX
TWISummit 2019 - Take the Pain out of Browser Automation!
PDF
Creating a flawless user experience, end to-end, functional to visual - Slide...
PDF
Intro to JavaScript Testing
PDF
Collaborating with Developers: How-to Guide for Test Engineers - By Gil Tayar
PDF
Types of Functional Testing Every QA Must Know
PPT
Selenium
PPT
selenium.ppt
PPT
selenium.ppt
PDF
Testing in FrontEnd World by Nikita Galkin
PPT
selenium.ppt
PDF
The Testing Planet Issue 2
PPT
Acceptance Testing With Selenium
PDF
Никита Галкин "Testing in Frontend World"
PPT
By combining Selenium for frontend testing and tools
PDF
'NO EXCUSES FOR NOT WRITING TESTS!' by ANDRII SHUMADA @OdessaJS'2020
PDF
Lesson 01 - KTPM - Introduction To Software Testing (P1).pdf
PDF
Intro To JavaScript Unit Testing - Ran Mizrahi
Visual Testing: The Missing Piece of the Puzzle -- presentation by Gil Tayar
Mastering UI automation at Scale: Key Lessons and Best Practices (By Fernando...
Functional Testing - A Detailed Guide.pdf
TWISummit 2019 - Take the Pain out of Browser Automation!
Creating a flawless user experience, end to-end, functional to visual - Slide...
Intro to JavaScript Testing
Collaborating with Developers: How-to Guide for Test Engineers - By Gil Tayar
Types of Functional Testing Every QA Must Know
Selenium
selenium.ppt
selenium.ppt
Testing in FrontEnd World by Nikita Galkin
selenium.ppt
The Testing Planet Issue 2
Acceptance Testing With Selenium
Никита Галкин "Testing in Frontend World"
By combining Selenium for frontend testing and tools
'NO EXCUSES FOR NOT WRITING TESTS!' by ANDRII SHUMADA @OdessaJS'2020
Lesson 01 - KTPM - Introduction To Software Testing (P1).pdf
Intro To JavaScript Unit Testing - Ran Mizrahi
Ad

More from Applitools (20)

PDF
Applitools Platform Pulse: What's New and What's Coming - July 2025
PDF
Code and No-Code Journeys: The Maintenance Shortcut
PDF
Code and No-Code Journeys: The Coverage Overlook
PDF
Creating Automated Tests with AI - Cory House - Applitools.pdf
PDF
Navigating EAA Compliance in Testing.pdf
PDF
AI-Assisted, AI-Augmented & Autonomous Testing
PDF
Code or No-Code Tests: Why Top Teams Choose Both
PDF
The ROI of AI-Powered Testing, presented by Applitools
PDF
Building No-code Autonomous E2E Tests_Applitools.pdf
PDF
Conquer 6 Testing Challenges_Applitools.pdf
PDF
Autonomous End-to-End Testing for Online Banking Applications Presented with ...
PDF
Playwright Visual Testing Best Practices, presented by Applitools
PDF
Cross-Browser and Cross-Device Testing | Applitools in Action
PDF
Advanced Debugging Techniques | Applitools in Action.pdf
PDF
AI-Powered Testing Strategies for the Seasonal Shopping Surge.pdf
PDF
Test Automation for Dynamic Applications _ Applitools in Action.pdf
PDF
Proven Approaches to AI-Powered E2E Testing.pdf
PDF
Applitools Autonomous 2.0 Sneak Peek.pdf
PDF
Building the Ideal CI-CD Pipeline_ Achieving Visual Perfection
PDF
Leveraging AI for Mobile App Testing on Real Devices | Applitools + Kobiton
Applitools Platform Pulse: What's New and What's Coming - July 2025
Code and No-Code Journeys: The Maintenance Shortcut
Code and No-Code Journeys: The Coverage Overlook
Creating Automated Tests with AI - Cory House - Applitools.pdf
Navigating EAA Compliance in Testing.pdf
AI-Assisted, AI-Augmented & Autonomous Testing
Code or No-Code Tests: Why Top Teams Choose Both
The ROI of AI-Powered Testing, presented by Applitools
Building No-code Autonomous E2E Tests_Applitools.pdf
Conquer 6 Testing Challenges_Applitools.pdf
Autonomous End-to-End Testing for Online Banking Applications Presented with ...
Playwright Visual Testing Best Practices, presented by Applitools
Cross-Browser and Cross-Device Testing | Applitools in Action
Advanced Debugging Techniques | Applitools in Action.pdf
AI-Powered Testing Strategies for the Seasonal Shopping Surge.pdf
Test Automation for Dynamic Applications _ Applitools in Action.pdf
Proven Approaches to AI-Powered E2E Testing.pdf
Applitools Autonomous 2.0 Sneak Peek.pdf
Building the Ideal CI-CD Pipeline_ Achieving Visual Perfection
Leveraging AI for Mobile App Testing on Real Devices | Applitools + Kobiton

Recently uploaded (20)

PDF
wealthsignaloriginal-com-DS-text-... (1).pdf
PDF
Design an Analysis of Algorithms II-SECS-1021-03
PPTX
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
PDF
Digital Strategies for Manufacturing Companies
PPTX
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
PPTX
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
PDF
medical staffing services at VALiNTRY
PDF
How to Choose the Right IT Partner for Your Business in Malaysia
PDF
Which alternative to Crystal Reports is best for small or large businesses.pdf
PDF
How to Migrate SBCGlobal Email to Yahoo Easily
PPTX
Transform Your Business with a Software ERP System
PPTX
CHAPTER 2 - PM Management and IT Context
PPTX
Essential Infomation Tech presentation.pptx
PDF
System and Network Administraation Chapter 3
PPTX
Operating system designcfffgfgggggggvggggggggg
PPTX
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
PDF
System and Network Administration Chapter 2
PDF
Wondershare Filmora 15 Crack With Activation Key [2025
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
PDF
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free
wealthsignaloriginal-com-DS-text-... (1).pdf
Design an Analysis of Algorithms II-SECS-1021-03
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
Digital Strategies for Manufacturing Companies
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
medical staffing services at VALiNTRY
How to Choose the Right IT Partner for Your Business in Malaysia
Which alternative to Crystal Reports is best for small or large businesses.pdf
How to Migrate SBCGlobal Email to Yahoo Easily
Transform Your Business with a Software ERP System
CHAPTER 2 - PM Management and IT Context
Essential Infomation Tech presentation.pptx
System and Network Administraation Chapter 3
Operating system designcfffgfgggggggvggggggggg
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
System and Network Administration Chapter 2
Wondershare Filmora 15 Crack With Activation Key [2025
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free

Visual Testing: Turbo-Charge Your Functional Tests with Visual Powers in Just 8 Lines of Code -- by Gil Tayar

  • 1. @giltayar You’re almost there: turbocharge your functional tests with visual powers Gil Tayar (@giltayar) August 2018 This presentation: http://bit.ly/turbocharge-webinar Github repo: https://github.com/giltayar/realworld-frontend-testing
  • 2. @giltayar@giltayar About Me ● My developer experience goes all the way back to the ‘80s. ● Am, was, and always will be a developer ● Testing the code I write is my passion ● Currently evangelist and architect @ Applitools ● We deliver Visual Testing tools: If you’re serious about testing, checkout Applitools Eyes ● Sometimes my arms bend back ● But the gum I like is coming back in style @giltayar
  • 5. @giltayar Tests, Tests, Glorious Tests! ● Unit ● Integration ● E2E ● Acceptance ● Visual ● Contract ● Browser ● Automation ● Functional ● And the list goes on…
  • 6. @giltayar Not to mention the different interpretations
  • 8. @giltayar Functional tests are automation tests that automate the browser in order to test the functionality of a web application* * Or a mobile app
  • 9. @giltayar Functional tests are automation tests that automate the browser in order to test the functionality of a web application* * Or a mobile app
  • 10. @giltayar Let’s write a Functional Test
  • 11. @giltayar ● Huge library (npm) ○ Biggest. ○ Very high quality ○ Lots of tests! ● DLL hell is gone (almost…) ● No bullshit libraries (readme-s, not javadocs) I love NodeJS ● Constantly improving in performance ● Fast startup times ● ES2018 is a wonderful language ● Async programming is wonderful ● Good FP language too ● Constantly evolving
  • 12. @giltayar ● All the cool new automation frameworks are in NodeJS/JavaScript ● It is the language of the frontend developers ○ Writing tests with the frontend developers ○ Code sharing! I love NodeJS
  • 13. @giltayar Let’s give a look at the app we’re testing
  • 14. @giltayar Let’s write a Functional Test in JavaScript! https://github.com/giltayar/realworld-frontend-testing, branch step1
  • 16. @giltayar All Functional Tests Look Like This ● Action ● Action ● Validation ● Action ● Validation ● Action ● Action ● Action ● Validation
  • 17. @giltayar Let’s do the following functional story https://github.com/giltayar/realworld-frontend-testing, branch step2
  • 18. @giltayar async function waitFor(selector) { await driver.wait( until.elementLocated(By.css(selector))) } async function setText(selector, text) { const field = await driver.findElement( By.css(selector)) await field.sendKeys(text) } Create some utility functions async function click(selector) { const element = await driver.findElement( By.css(selector)) await element.click() } async function getText(selector) { const element = await driver.findElement( By.css(selector)) return await element.getText() }
  • 20. @giltayar // action: browse to registration page await driver.get('http://localhost:3000/register' ) // action: set username, password, email to something await setText('input[placeholder=Username]' , 'ausername') await setText('input[placeholder=Password]' , 'aPassword') await setText('input[placeholder=Email]' , 'an@email.com' ) // action: submit form await click('button[type=submit]' ) Register the User
  • 21. @giltayar // action: browse to registration page await driver.get('http://localhost:3000/register' ) // action: set username, password, email to something await setText('input[placeholder=Username]' , 'ausername') await setText('input[placeholder=Password]' , 'aPassword') await setText('input[placeholder=Email]' , 'an@email.com' ) // action: submit form await click('button[type=submit]' ) Fragile Selector Problem Fragile selectors
  • 22. @giltayar class RegisterPage { async function registerUser(username, email, password) { // ... } } Example RegisterPage Object
  • 24. @giltayar await waitFor('img[alt=ausername]') // validate username expect(await getText('a[href="/@ausername"]')).to.equal('ausername') // validate articles list expect(await getText('.article-preview')) .to.equal('No articles are here... yet.') // validate active tab expect(await getText('.nav-link.active')).to.equal('Your Feed') Validate the Empty User Home Page
  • 26. @giltayar // action: click on new post const newPost = await driver.findElement (By.partialLinkText ('New Post')) await newPost.click() await waitFor('input[placeholder="Article Title"]' ) // action: set the title, description, and article await setText('input[placeholder="Article Title"]' , 'a title') await setText('input[placeholder="What's this article about?"]' , 'something' ) await setText('textarea[placeholder*="Write your article"]' , 'wonderful' ) // action: set the tags const tags = await driver.findElement (By.css('input[placeholder="Enter tags"]' )) for (const tag of ['a', 'b', 'c']) await tags.sendKeys(tag, Key.ENTER) // action: submit form await click('button[type=button]' ) Publish a Post
  • 27. @giltayar See that it’s there, and add a comment validation validation validation validation action action
  • 28. @giltayar await waitFor('h1') // validate title expect(await getText('h1')).to.equal('a title') // validate article content expect(await getText('.article-content')).to.include('wonderful') // validate tags expect(await getText('.tag-list')).to.equal('abc') Validate Post
  • 29. @giltayar // action: set comment await setText('textarea', 'a comment') // action: submit comment await click('button[type=submit]') Add Comment
  • 31. @giltayar await waitFor('div.card .card-block') // validate comment text expect(await getText('div.card .card-block')).to.equal('a comment') // validate comment username expect(await getText('div.card a.comment-author:nth-child(2)')) .to.equal('ausername') Validate Comment
  • 33. @giltayar // action: goto settings page await driver.get('http://localhost:3000/settings') await waitFor('button.btn-outline-danger') // action: click logout button await click('button.btn-outline-danger') Logout
  • 34. @giltayar Check Blog with anonymous user validation validation validation validation validation
  • 35. @giltayar // goto home page await driver.get('http://localhost:3000/') // validate post author expect(await getText('a.author')) .to.equal('ausername') // validate post title expect(await getText('.article-preview h1')) .to.equal('a title') // validate post description expect(await getText('.article-preview h1 + p')) .to.equal('something') // validate post tags expect(await getText('.article-preview ul')) .to.equal('abc') Validate Home Page for Anonymous User // validate popular tags expect(await getText('.sidebar .tag-list')) .to.equal('abc') // action: goto post page await click('.article-preview h1') await waitFor('.article-page') // validate post title expect(await getText('h1')).to.equal('a title') // validate article description expect(await getText('div.article-content p')) .to.equal('wonderful') // validate article tags expect(await getText('ul.tag-list')).to.equal('abc')
  • 36. @giltayar Problems with Actions ● Fragile Selectors ● Complex Code ● Solution: Page Objects
  • 37. @giltayar Problems with Validations ● Fragile Selectors ● Complex Code ● Too much to validate ● Only positive check ○ We don’t validate what we don’t know about ● Solution: Page Objects, partially
  • 39. @giltayar Let’s see why validations are holistic ● Add the following to Register.js: onSubmit: (username, email, password) => { dispatch({ type: UPDATE_FIELD_AUTH, key: 'username', value: '' }) const payload = agent.Auth.register(username, email, password); ● This will clear the username after every submit
  • 40. @giltayar Let’s see what the effect of this bug will be
  • 41. @giltayar But does the test pass? Yes, it does!
  • 43. @giltayar Let’s revert the “bug”, rebuild, and figure out how to solve the validation problem
  • 48. @giltayar Let’s do some live coding for that! https://github.com/giltayar/realworld-frontend-testing, branch step3
  • 49. @giltayar So we’ve replace this... expect(await getText('.error-messages' )).to.include("email can't be blank") With this… expect(Buffer.from(await driver.takeScreenshot (), 'base64')).to.matchImage( 'registration-blank-email-error' , )
  • 50. @giltayar Why is this better? It’s just one line replacing another
  • 51. @giltayar Solutions to problems with validations ● Fragile Selectors ○ No more selectors ● Complex Code ○ Same code for all validations ● Too much to validate ○ We validate holistically ● Only positive check ○ We validate things we didn’t even think of validating
  • 52. @giltayar Solutions to problems with validations ● Fragile Selectors ○ No more selectors ● Complex Code ○ Same code for all validations ● Too much to validate ○ We validate holistically ● Only positive check ○ We validate things we didn’t even think of validating
  • 53. @giltayar Let’s try it out... ● Run the tests without the bug ● Copy baseline image to expected-screenshots folder ● Add the following to Register.js: onSubmit: (username, email, password) => { dispatch({ type: UPDATE_FIELD_AUTH, key: 'username', value: '' }) const payload = agent.Auth.register(username, email, password); ● This will clear the username after every submit
  • 54. @giltayar Functional vs Visual Validations ● Let’s test it in step2: functional validations git checkout step2 && npm run build && npm test ● The test passes! (and it shouldn’t) ● Now let’s try step3: visual validations git checkout step2 && npm test ● The test fails! (as it should)
  • 55. @giltayar Let’s revert the “bug”, rebuild, and continue looking at visual vs. functional valiation
  • 56. @giltayar Functional Validations are Focused Visual Validations are Holistic
  • 57. @giltayar async function validatePost() { await waitFor('.validate-content') // validate title expect(await getText('h1')).to.equal('a title') // validate article content expect(await getText('.article-content')).to.include('wonderful') // validate tags expect(await getText('.tag-list')).to.equal('abc') } And we’ve change this...
  • 58. @giltayar async function validatePost() { await waitFor('.article-content') await checkWindow('new-post') } Into this...
  • 59. @giltayar // goto home page await driver.get('http://localhost:3000/') // validate post author expect(await getText('a.author')) .to.equal('ausername') // validate post title expect(await getText('.article-preview h1')) .to.equal('a title') // validate post description expect(await getText('.article-preview h1 + p')) .to.equal('something') // validate post tags expect(await getText('.article-preview ul')) .to.equal('abc') And this long code... // validate popular tags expect(await getText('.sidebar .tag-list')) .to.equal('abc') // action: goto post page await click('.article-preview h1') await waitFor('.article-page') // validate post title expect(await getText('h1')).to.equal('a title') // validate article description expect(await getText('div.article-content p')) .to.equal('wonderful') // validate article tags expect(await getText('ul.tag-list')).to.equal('abc')
  • 60. @giltayar async function validateBlog() { await driver.get('http://localhost:3000/') await checkWindow('anonymous-home-page') await click('.article-preview h1') await waitFor('.article-page') await checkWindow('anonymous-blog-post-view') } Into this...
  • 61. @giltayar Solutions to problems with validations ● Fragile Selectors ○ No more selectors ● Complex Code ○ Same code for all validations ● Too much to validate ○ We validate holistically ● Only positive check ○ We validate things we didn’t even think of validating
  • 62. @giltayar Problems with Naive Image Diffing ● Small anti-aliasing differences ○ Mac vs. Linux vs. Windows ○ Different GPUs ● Date/time problems ○ regions that are different from run to run ● Full screen vs. Viewport ● Comparing is a pain ● “Accepting” a new baseline is a pain
  • 63. @giltayar Problems with Naive Image Diffing ● Small anti-aliasing differences ○ Mac vs. Linux vs. Windows ○ Different GPUs ● Date/time problems ○ regions that are different from run to run ● Full screen vs. Viewport ● Comparing is a pain ● “Accepting” a new baseline is a pain
  • 65. @giltayar Cloud-based solution ● Visual diffing algorithms see like a human sees ○ Ignoring the small differences ○ AI-level algorithms are now involved and keep getting better ● Screenshot management tools are available, enabling you to ○ See the diffs ○ Approve new baselines ○ Open bugs on diffs that are bugs
  • 66. @giltayar Let’s Run the Demo https://github.com/giltayar/realworld-frontend-testing, branch step4
  • 67. @giltayar Let’s change some code to see some of the baseline management tooling
  • 69. @giltayar Supercharging Your Test with Visual Validations ● Write code that issues action after action, to create a long story ○ You can use a recorder like Selenium IDE or one of the new cloud-based ones ● Intersperse your code with visual validations ○ You can use the open-source solutions or a cloud-based option ● You get two for the price of one: ○ Much simpler functional validations ○ Visual validation of your page: catch those visual bugs that you couldn’t catch till now!
  • 70. @giltayar Thank You Gil Tayar (@giltayar) This presentation: http://bit.ly/turbocharge-webinar The Github repo: https://github.com/giltayar/realworld-frontend-testing