Tiffany Le-Nguyen
Staff Frontend Engineer at Netlify ✈️
Hallo I'm Tiffany.
Engineer at ExpediaGroup and newbie speaker 🐣.
Mentor for freeCodeCamp Montreal
Work on terminal shell Starship sometimes.
twitter/sirmerr
github.com/sirmerr
linkedin.com/in/tiffanyln
This visual test caught that the image changed from Banff National Park to a screaming frog 🐸
Type your answers in the group chat 🕐
This snapshot test caught that the DOM structure for the heading has changed.
The dev removed a div.
<div>
<h1>
Find hotels near Banff National Park, Alberta
</h1>
</div>
<h1>
Find hotels near Banff National Park, Alberta
</h1>
--<div>
--</div>
Type your answers in the group chat 🕐
<div>
<h1>
Find hotels near Banff National Park, Alberta
</h1>
</div>
<h1>
Find hotels near Banff National Park, Alberta
</h1>
--<div>
--</div>
please don't use snapshot tests they're 99% chaotic bad and will make your devs cry more than usual
please don't use snapshot tests they're 99% chaotic bad and will make your devs cry more than usual
All customers
Want to enter dates and search for properties
Want to enter dates and search for properties
Data Analysts
Want to track events of the customer through the funnel
All customers
Bots (SEO)
Want to enter dates and search for properties
Wants good metadata, performance and accessibility
Data Analysts
Want to track events of the customer through the funnel
All customers
Visual
modified testing pyramid by Kent C. Dodds & Martin Fowler
🐇
🐢
cross-device
💵
💰💰💰
- Using class-based selectors to find elements
- Using DOM structure to find elements
- Manually triggering the re-rendering of components
- Using fixed-duration waits when expecting state changes
- Simulating browser events
// Instead of using
const usernameInput = container.querySelectorAll('input')[0];
const passwordInput = wrapper.find('input').last();
// ...or using
const usernameInput = wrapper.find('.sign-in-modal .username-field');
const passwordInput = container.querySelector('.sign-in-modal .password-field');
// Instead of using
// const usernameInput = container.querySelectorAll('input')[0];
// const passwordInput = wrapper.find('input').last();
// ...or using
// const usernameInput = wrapper.find('.sign-in-modal .username-field');
// const passwordInput = container.querySelector('.sign-in-modal .password-field');
// We should consider
const usernameInput = getByLabelText('Username');
const passwordInput = getByLabelText('Password');
// A shortcut for document.querySelector(`[data-testid="${yourId}"]`)
// <input data-testid="username-input" />
const usernameInputElement = getByTestId(container, 'username-input')
Should only be used in situations where all other selectors don't work for your use case.
// In a situation where you'd want to select a specific item in a list
const thirdLiInUl = container.querySelector('ul > li:nth-child(3)')
// You can include an index or ID in your attribute
const items = [
/* your items */
]
const thirdItem = getByTestId(`item-${items[2].id}`)
All customers
Data Analysts
All customers
Data Analysts
Checking the inputs and images are in the DOM
expect(queryByLabelText('Check-in')).tobeIntheDocument();
expect(queryByAltText('Frog screaming')).toBeInTheDocument();
All customers
Data Analysts
Checking the inputs and images are in the DOM
Checking the track event sent on click
expect(queryByLabelText('Check-in')).tobeIntheDocument();
expect(queryByAltText('Frog screaming')).toBeInTheDocument();
const searchButton = getByText('Search');
search.click();
expect(trackEvent).toHaveBeenCalledWith(...);
All customers
Data Analysts
Checking the inputs and images are in the DOM
expect(queryByLabelText('Check-in')).tobeIntheDocument();
expect(queryByAltText('Frog screaming')).toBeInTheDocument();
const searchButton = getByText('Search');
search.click();
expect(trackEvent).toHaveBeenCalledWith(...);
or Checking error states from bad customer input and bad API responses
Checking the track event sent on click
All customers
Actually building and running the app locally or in CI and interacting with the page the way a user would
All customers
// Enter check in and out dates
cy.findByLabelText('Check-in').type('June 23, 2020');
cy.findByLabelText('Check-out').type('June 24, 2020');
// Submit dates
cy.findByText('Send').click();
// Check it sends to the correct url with query params
cy.location().should(loc => {
expect(loc.pathname).to.eq('/Hotel-Search');
expect(loc.search).to.eq('?startDate=2020-06-23&endDate=2020-06-23')
})
All customers
Most smaller applications don't need this
Useful when you have external UI libraries. Beware of CI font differences.
All customers
cy.matchScreenshot();
Bots (SEO)
All customers
All customers
All customers
By Tiffany Le-Nguyen
The present-day web: interactions are complex, accessibility's a priority, performance is essential and UI updates keep us on our toes. How can we have confidence that changes won't cause unwanted side effects, while minimally burdening devs? Let's walk through a modern end-to-end testing suite, which puts users first, for a maintainable and scalable codebase. ExpediaGroup has hundreds of devs and millions of users. Changes have to be airtight, easily refactorable, and consistent to users. Over the year, Tiffany built a user-centric testing suite that is now the standard at Expedia. She'll be sharing how modern web apps can define a self-aligning testing methodology, from unit to visual regression that will adapt to change. Attendees will be given an overview of how to identify their users and test for their individual needs while balancing cost and confidence in their systems.