Everything You Need to Know About Safari Testing with Cypress

As an experienced testing professional with over 10 years of expertise across thousands of real devices and browsers, I often get asked about best practices for testing Safari and the WebKit engine using Cypress.

Safari has continued to gain adoption over the years. With over 19% market share globally as of January 2023 and growing installation base across Apple devices, having robust test automation for Safari is crucial.

However, Safari and WebKit pose unique challenges for test automation that many web teams struggle with. In this comprehensive 3000+ word guide, I’ll share my proven insights for effectively testing Safari using Cypress.

Why Safari Testing Matters

Since Safari ships as the default browser across all Apple devices including Macs, iPhones and iPads, it has seen growing usage:

Global Safari Usage Share

Year Desktop Mobile Total
2018 3.78% 15.14% 9.84%
2019 3.44% 17.33% 11.84%
2020 3.31% 19.92% 13.86%
2021 3.07% 23.82% 16.18%
2022 2.83% 23.59% 16.95%

As the second largest browser globally, flawless Safari support is vital for web teams targeting Apple device users.

However, Safari has notoriously lagged behind Chromium-based browsers in supporting newer web standards:

Global Standards Support (source)

Browser Standards Support
Chrome 93%
Firefox 92%
Safari 81%

And differences in the JavaScript engine and browser capabilities can lead to cross-browser bugs unique to Safari.

This makes comprehensive testing in Safari imperative to ensure parity across browsers. But setting up sustainable test automation for Safari can be tricky compared to other mainstream browsers.

Cypress Capabilities for Cross-Browser Testing

As a next-gen front-end testing tool, Cypress is designed to handle many of the flake issues that plague UI testing.

Tests execute directly inside the real browser alongside your application. Cypress automatically synchronizes with the app via advanced bindings and commands.

Out of the box, Cypress supports:

✅ Chrome
✅ Chromium-based Edge
✅ Firefox
✅ Electron

For other browsers like Safari, Cypress utilizes WebDriver protocol to proxy browser automation. But this adds overhead compared to the direct model.

Still, with its user-centric workflows and customizability Cypress provides a reliable framework for testing Safari.

Challenges with Automated Safari Testing

Since Apple strictly allows Safari to run only on Apple hardware, testing it at scale runs into multiple constraints:

Licensing Terms

Apple‘s EULA forbids running macOS/Safari on non-Apple hardware. So solutions that containerize Safari using Docker/VMs violate the agreement.

Partial Parity

There are WebKit browsers for Linux. But these do not exactly mimic the behavior of official Safari on macOS.

Inaccessible Platform

Unlike Windows or Linux, real macOS devices are harder to access for most test teams.

Flaky Automation

Safari is particularly prone to flakiness – timing issues, inconsistent behavior, etc. Makes reliable automation difficult.

Feature Gaps

Safari often lags behind in supporting newer JavaScript APIs and standards – needing workarounds.

Limited Debugging

You miss out on DevTools features that are beneficial for testing – console, network logs, element inspection, etc. Especially with a cloud testing provider.

Cost Overheads If opting for cloud Safari access, large parallel test suites incur significant platform costs due to device demand.

These factors make Safari testing complex compared to other major browsers. But with the right setup and testing practices, you can achieve reliable automation.

Next, let‘s see how Cypress enables Safari test runs.

Experimental Safari Support in Cypress

Cypress offers opt-in support for WebKit – the browser engine used by Safari. Here are the basics to enable it:

Step 1: Install Dependency

The playwright-webkit package contains binaries needed to launch a WebKit browser.

npm install playwright-webkit --save-dev

Step 2: Enable Experiment

In your Cypress config file, flag the experiment:

// cypress.config.js
const { defineConfig } = require(‘cypress‘)

module.exports = defineConfig({
  experimentalWebKitSupport: true 
})

Step 3: Install Linux Dependencies

On Linux systems, install extra dependencies required:

npx playwright install-deps webkit

Once set up, you will see a new WebKit browser option when launching Cypress. Tests now execute in the WebKit engine used by Safari.

10 Tips for Smooth Safari Testing

While the experimental Cypress integration is great for early Safari feedback, you’re likely to face hiccups with real-world apps and test suites.

Here are 10 tips I’ve compiled for reliable Safari test automation based on countless hours debugging tricky defects on apps since the iPhone 3G days.

1. Handle Async code and Promises

Safari tends to resolve JavaScript promises slightly slower than Chromium browsers under load. This can lead to flakiness for tests with chained async actions.

Introduce waits between promise-returning steps so subsequent commands don’t execute prematurely:

cy.get(‘.btn‘).click()
  .wait(500) // Wait 500ms for promises to settle 
  .get(‘.result‘).should(‘be.visible‘) 

Or assert for specific UI state before moving forward:

cy.get(‘.btn‘).click().then(() => {

  // Confirm click worked before next action
  cy.get(‘.btn‘).should(‘have.css‘, ‘opacity‘, ‘0.5‘)  

})
.get(‘.result‘) 

2. Retry Intermittent Failures

It’s common to encounter flaky timeouts and element not found errors when testing Safari. Rather than debugging each one-off issue, use retry-ability to auto-handle failures:

Cypress.Commands.add(‘retry‘, {
  prevSubject: true,

  retries: 3  // Retry up to 3 times
})

// Usage
cy.get(‘.selector‘).retry().should(‘be.visible‘)

This saves you having to explicitly wrap assertions in retries everywhere.

3. Assert for Visual Cues

Along with standard assertions, also check for visual cues in the UI to confirm state:

// Button turns blue on click  

cy.get(‘.btn‘).click().should(‘have.css‘, ‘background-color‘, ‘rgb(0, 0, 255)‘)

This helps avoid false positives when the DOM updates but visual state is not yet reflected.

4. Debug with Screenshots

Safari automation errors often don’t surface clear details on the failure cause. Use screenshots to debug what the browser rendered right before assertions:

cy.get(‘.btn‘).click().screenshot(‘after-click‘) 
  .get(‘.result‘).should(‘be.visible‘)

Review the screenshot to understand what could have caused the visibility check to fail.

5. Mock Server Responses

Fake backend responses to avoid async delays and flakiness from real API calls:

cy.intercept(‘GET‘, ‘/api/users‘, {
  fixture: ‘users.json‘
}).as(‘users‘)

cy.visit(‘/‘) 

// Requests for /api/users will get fixtures/users.json  
cy.wait(‘@users‘)   

This helps isolate just the front-end behavior under test.

6. Disable Animations and Transitions

Safari handles certain CSS animations and transitions differently leading to timeline issues.

Disable these via test code so interactions use 0ms timing:

cy.document().then(document => {

  // Disable all transitions
  document.documentElement.style.transition = ‘none‘

  // Disable animations
  document.documentElement.style.animation = ‘none‘ 

})

Now actions like clicks, hover, asserts have no animation lag.

7. Spoof User Agent

Render tests using different Safari user agents by spoofing request headers:

const userAgent = ‘Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X)...‘;  

Cypress.on(‘window:before:load‘, win => {
  Object.defineProperty(win.navigator, ‘userAgent‘, {
    value: userAgent
  })
})

This allows testing views across Safari on macOS, iPadOS or older iOS versions.

8. Bump Timeout Thresholds

Some interactions might take longer to complete in Safari vs Chromium. Bump Cypress base timeouts to avoid false failures:

// cypress.json
{
  "defaultCommandTimeout": 10000, // 10 sec 
  "pageLoadTimeout": 60000 // 60 sec
}

Use higher response times for visits, route transitions, assertions etc.

9. Shim Missing Features

Safari has historically lagged in supporting newer JavaScript APIs compared to other browsers.

For APIs missing in the Safari version under test, inject polyfills as needed:

// Polyfill Array.prototype.flat()
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat#Polyfill

if (!Array.prototype.flat) {
  Array.prototype.flat = function flat () {
    // Polyfill implementation  
  }
}

Cypress.on(‘window:before:load‘, win => {
  win.Array.prototype.flat = flat // Inject shim
})

Now tests can safely use .flat() even if the native method doesn’t exist.

10. Raise Awareness on Gaps

When testing Safari, note down gaps like missing features, CSS rendering issues, layout differences etc. compared to other browsers.

Share artifacts like screenshots demonstrating defects with your team. It helps prioritize fixes and reduce browser compatibility debt.

These tips should help smooth over common pain points when testing Safari with Cypress. But you’re still likely to encounter browser-specific gremlins. Next let’s see how to debug these issues.

Debugging Tricky Safari Issues

Despite following best practices, you‘ll occasionally face obscure Safari testing issues without clear diagnoses.

Here are my top debugging strategies for these scenarios:

Log Internal Browser Errors

Capture JavaScript errors inside Safari using:

// Logs browser errors to Cypress
window.onerror = (message, source, lineno, colno, error) => {

  console.error(error) // Or use Cypress._logs 

  return false
}

Cypress.on(‘window:before:load‘, win => {
  // Inject error logging
})

Now internal exceptions during test runs are surfaced.

Inspector via BrowserStack

If testing on BrowserStack, use the interactive Inspector when tests fail:

BrowserStack Inspector

This gives you access to DOM, console, network traffic – similar to browser DevTools.

Screenshots & Videos

Use Cypress screenshots and videos features to visually debug the last rendered content and interactions.

Diff screenshots against a known good state to isolate differences.

Try on Actual Devices

Some behaviors are impossible to replicate except on real Apple hardware with genuine Safari.

So swap cloud services for physical devices when facing unexplainable test failures.

Debug in Isolation

Narrow down error causes through isolated debugging:

  • Test suspected components individually
  • Reduce irrelevant test setup and dependencies
  • Mock network calls using fixtures or static data
  • Remove unnecessary stubs, spies, clocks
  • Incrementally validate individual pieces

With enough isolated probing, you can pinpoint the exact failure stimulus.

While tricky bugs are inevitable, methodically applying these debugging techniques helps resolver even the most obscure Safari testing issues.

Cloud Testing Services

Lacking access to Apple hardware for testing Safari should not block your release cycles. Cloud-based services give you on-demand access to real Safari browsers running on remote macOS devices.

BrowserStack

BrowserStack provides automated and manual testing capability for Safari on various device types, OS versions and browser versions.

Add the BrowserStack Cypress integration and specify browsers in cypress.json:

"browsers": [
  {
    "os": "OS X",
    "os_version": "Monterey",
    "browser": "Safari",
    "browser_version": "16.0"
  } 
]

Then directly run tests against those BrowserStack browsers:

cypress run --browser browserstack  

Some downsides to note with BrowserStack-hosted Safari testing:

  • Limited older OS and Safari versions
  • No support for experimental Cypress features
  • Reduced debugging without browser DevTools
  • Cost overheads if running large test suites

Local Testing Labs

Services like TestingBot and Sauce Labs offer remote access to macOS devices as well. So real Safari testing can integrate into CI builds.

Pricing is generally based on concurrency and test minutes used.

Closing Thoughts

In this actionable guide, we covered topics like:

  • The growing relevance of Safari testing
  • Cypress support and setup for WebKit automation
  • Practical tips to smooth common pain points
  • Debugging methodologies for tricky defects
  • Cloud testing services enabling CI

I hope these end-to-end insights on Cypress Safari testing – compiled from countless hours of hands-on experience – prove useful in taking your test automation to the next level.

Safari and WebKit testing can definitely feel like the wild west compared to the stability of Chrome and Firefox test suites. But systematically addressing the various challenges gives you safety net for Apple browser users.

Let me know if any questions come up or if you need help investigating obscure Safari issues! I work extensively with web teams in locking down their browser test coverage and would be glad to help however I can.

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.