-
Notifications
You must be signed in to change notification settings - Fork 4
feat: Introduce Playwright for e2e testing #1007
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
34329e6 to
9c0ff34
Compare
9c0ff34 to
bd1e33a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces Playwright for end-to-end testing of SDK flows, providing comprehensive test coverage across major features including company onboarding, employee onboarding, contractor management, and payroll processing.
Changes:
- Added Playwright infrastructure with CI integration and test harness app
- Extended MSW mock handlers to support e2e test scenarios
- Created 15 e2e tests covering 6 major flows
Reviewed changes
Copilot reviewed 24 out of 25 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| playwright.config.ts | Configures Playwright with Chromium browser, test directory, and dev server settings |
| package.json | Adds e2e test scripts and MSW worker directory configuration |
| eslint.config.mjs | Excludes e2e directory from linting |
| vite.config.ts | Excludes e2e directory from unit test collection |
| .gitignore | Ignores Playwright test artifacts and MSW service worker file |
| .github/workflows/ci.yaml | Adds e2e job to CI pipeline with Playwright browser installation and test execution |
| e2e/vite.config.ts | Configures Vite build for e2e test app with React and SCSS support |
| e2e/index.html | Provides HTML entry point for e2e test harness |
| e2e/main.tsx | Implements flow renderer that loads different SDK flows based on URL parameters |
| e2e/mocks/browser.ts | Sets up MSW browser worker for API mocking in e2e tests |
| e2e/tests/*.spec.ts | Implements e2e tests for company onboarding, employee onboarding, contractor flows, and payroll |
| src/test/mocks/handlers.ts | Exports additional handlers and imports new mock modules for e2e support |
| src/test/mocks/apis/*.ts | Adds and updates mock API handlers to support e2e test scenarios |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Add Playwright configuration with chromium browser support - Add e2e test application with MSW mocking - Add test specs for major flows: - Company onboarding - Employee onboarding - Employee self-onboarding - Contractor onboarding - Contractor payments - Payroll - Extend MSW handlers for e2e scenarios - Add CI job for running e2e tests with artifact upload
5cb4d4f to
da31686
Compare
serikjensen
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good! couple of comments
Also, question: are we removing the existing tests while we add these ones to playwright? or will that be a fast follow? Just worried about having both side by side
| import { defineConfig } from 'vite' | ||
| import react from '@vitejs/plugin-react-swc' | ||
| import svgr from 'vite-plugin-svgr' | ||
| import { resolve } from 'path' | ||
|
|
||
| export default defineConfig({ | ||
| root: resolve(__dirname), | ||
| publicDir: resolve(__dirname, 'public'), | ||
| plugins: [ | ||
| react(), | ||
| svgr({ | ||
| svgrOptions: { | ||
| exportType: 'default', | ||
| titleProp: true, | ||
| }, | ||
| include: ['**/*.svg?react', '**/*.svg'], | ||
| }), | ||
| ], | ||
| resolve: { | ||
| alias: { | ||
| '@': resolve(__dirname, '../src'), | ||
| }, | ||
| }, | ||
| css: { | ||
| preprocessorOptions: { | ||
| scss: { | ||
| api: 'modern-compiler', | ||
| additionalData: `@use "@/styles/Helpers" as *; @use '@/styles/Responsive' as *;\n`, | ||
| }, | ||
| }, | ||
| }, | ||
| server: { | ||
| port: 5173, | ||
| }, | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we update this to share the existing vite config where possible so it can scale?
| const getLabel = () => { | ||
| if (paymentType === 'Payroll') { | ||
| return payrollRange | ||
| ? t('selectLabelPayroll', { payrollRange }) | ||
| : t('selectFallback') | ||
| } | ||
| return t('selectFallback') | ||
| } | ||
|
|
||
| return { | ||
| label: | ||
| paymentType === 'Payroll' | ||
| ? payrollRange | ||
| ? t('selectLabelPayroll', { payrollRange }) | ||
| : t('selectFallback') | ||
| : paymentType === 'ContractorPaymentGroup' | ||
| ? t('selectLabelContractorPaymentGroup', { | ||
| requestedAmount: formatCurrency(Number(requestedAmount)), | ||
| }) | ||
| : t('selectFallback'), | ||
| label: getLabel(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we know why it is making changes here? Seems like if there's an issue this one should be a sep PR
Add support for running Playwright e2e tests against real APIs in addition to MSW mocks. Three modes are now available: - `npm run test:e2e` -- MSW mocks (unchanged, used in CI) - `npm run test:e2e:local` -- local GWS-Flows + ZP instance - `npm run test:e2e:demo` -- live demo env (flows.gusto-demo.com) The demo mode auto-provisions a fresh company via GWS-Flows, creates test entities (employees, contractors, locations), and refreshes expired tokens automatically. Tests are data-agnostic and work with dynamically generated data. Adds an `e2e-demo` CI job that runs the full suite against the demo environment on every push. Co-authored-by: Cursor <cursoragent@cursor.com>
The CI runner cannot reach flows.gusto-demo.com (likely requires VPN or internal network access). Mark the job as non-blocking until a runner with the appropriate network access is configured. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⚠️ 2 New Security Findings
The latest commit contains 2 new security findings.
Findings Note: 2 findings are displayed as inline comments.
Not a finding? Ignore it by adding a comment on the line with just the word noboost.
Scanner: boostsecurity - Semgrep
| } | ||
|
|
||
| async function postToApi<T>(endpoint: string, data: Record<string, unknown>): Promise<T> { | ||
| const response = await fetch(`${GWS_FLOWS_BASE}${endpoint}`, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CWE-918: Server-Side Request Forgery (SSRF)
Original Rule ID: rules_lgpl_javascript_ssrf_rule-node-ssrf
Details
The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
This rule detected user-controlled URLs being passed to Node.js HTTP client
libraries including
axios.get(), axios.post(), fetch(), http.get(),http.request(), needle(), request(), urllib.request(),superagent.get(), bent(), got.get(), net.connect(), andsocket.io-client.io(). When user input controls the destination URL of HTTPrequests without validation, Server-Side Request Forgery (SSRF) vulnerabilities
arise. SSRF allows attackers to force the server to make requests to internal
systems, cloud metadata endpoints (such as 169.254.169.254), or other
unauthorized destinations. This can expose internal APIs, databases,
administrative panels, or enable network scanning and pivoting attacks that
bypass firewall rules and network segmentation.
📘 Learn More
AI Remediation
Input validation using a strict regular expression was added to ensure that only safe, relative path endpoints (starting with '/') are accepted in fetchFromApi and postToApi. This helps prevent Server-Side Request Forgery (SSRF) by blocking user-controlled or malicious URLs from being used in internal HTTP requests. The fix enforces that only valid API endpoints on the intended host can be called, reducing the SSRF risk.
At line 91, do the following changes:
}
async function fetchFromApi<T>(endpoint: string): Promise<T> {
+ if (!/^\/[\w\-/.]+$/.test(endpoint)) {
+ throw new Error('Invalid endpoint path');
+ }
const response = await fetch(`${GWS_FLOWS_BASE}${endpoint}`)
if (!response.ok) {
throw new Error(`API request failed: ${response.status} ${response.statusText}`)At line 99, do the following changes:
}
async function postToApi<T>(endpoint: string, data: Record<string, unknown>): Promise<T> {
+ if (!/^\/[\w\-/.]+$/.test(endpoint)) {
+ throw new Error('Invalid endpoint path');
+ }
const response = await fetch(`${GWS_FLOWS_BASE}${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },| async function testToken(flowToken: string, companyId: string): Promise<boolean> { | ||
| try { | ||
| const response = await fetch( | ||
| `${GWS_FLOWS_BASE}/fe_sdk/${flowToken}/v1/companies/${companyId}/locations`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CWE-918: Server-Side Request Forgery (SSRF)
Original Rule ID: rules_lgpl_javascript_ssrf_rule-node-ssrf
Details
The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
This rule detected user-controlled URLs being passed to Node.js HTTP client
libraries including
axios.get(), axios.post(), fetch(), http.get(),http.request(), needle(), request(), urllib.request(),superagent.get(), bent(), got.get(), net.connect(), andsocket.io-client.io(). When user input controls the destination URL of HTTPrequests without validation, Server-Side Request Forgery (SSRF) vulnerabilities
arise. SSRF allows attackers to force the server to make requests to internal
systems, cloud metadata endpoints (such as 169.254.169.254), or other
unauthorized destinations. This can expose internal APIs, databases,
administrative panels, or enable network scanning and pivoting attacks that
bypass firewall rules and network segmentation.
📘 Learn More
AI Remediation
All instances where user-controlled URLs were sent to fetch() now include strict protocol/domain validation and local/internal IP restriction before any HTTP request. This mitigates SSRF risks by ensuring that dangerous or internal-address URLs are never requested. The fix enforces only http(s) schemes and blocks private, loopback, and cloud metadata endpoints to prevent server-side network abuse.
At line 66, do the following changes:
}
try {
+ // Validate demoPageUrl before making request to prevent SSRF
+ if (!/^https?:\/\//.test(demoPageUrl) || /^(https?:\/\/)?(localhost|127\.|10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|169\.254\.169\.254)/.test(demoPageUrl)) {
+ throw new Error('Blocked potentially unsafe url');
+ }
const response = await fetch(demoPageUrl, {
headers: { Accept: 'text/html' },
signal: AbortSignal.timeout(10000),At line 100, do the following changes:
if (!companyId) {
try {
- const companyResponse = await fetch(`${GWS_FLOWS_BASE}/fe_sdk/${flowToken}/v1/companies`, {
+ // Validate URL before making request to prevent SSRF
+ const companiesUrl = `${GWS_FLOWS_BASE}/fe_sdk/${flowToken}/v1/companies`;
+ if (!/^https?:\/\//.test(companiesUrl) || /^(https?:\/\/)?(localhost|127\.|10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|169\.254\.169\.254)/.test(companiesUrl)) {
+ throw new Error('Blocked potentially unsafe url');
+ }
+ const companyResponse = await fetch(companiesUrl, {
signal: AbortSignal.timeout(10000),
})
if (companyResponse.ok) {At line 187, do the following changes:
async function testToken(flowToken: string, companyId: string): Promise<boolean> {
try {
+ const locationsUrl = `${GWS_FLOWS_BASE}/fe_sdk/${flowToken}/v1/companies/${companyId}/locations`;
+ // Validate URL before making request to prevent SSRF
+ if (!/^https?:\/\//.test(locationsUrl) || /^(https?:\/\/)?(localhost|127\.|10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|169\.254\.169\.254)/.test(locationsUrl)) {
+ throw new Error('Blocked potentially unsafe url');
+ }
const response = await fetch(
- `${GWS_FLOWS_BASE}/fe_sdk/${flowToken}/v1/companies/${companyId}/locations`,
+ locationsUrl,
{ signal: AbortSignal.timeout(5000) },
)
return response.ok
Summary
This PR introduces Playwright for end-to-end testing of SDK flows.
What's included
Infrastructure
e2e/) with its own Vite config and MSW mockinge2ejob in GitHub Actions that:Test Coverage
Initial tests for all major flows:
Running Locally
Why Playwright?