diff --git a/.gitignore b/.gitignore index a2bcdae6..1ede8c39 100644 --- a/.gitignore +++ b/.gitignore @@ -57,4 +57,15 @@ npm-debug.log* dist/ docs/api/ -site/ \ No newline at end of file +site/ + +# E2E test artifacts +playwright-report/ +test-results/ +playwright/.cache/ +tests/e2e/test-apps/**/dist/ +tests/e2e/test-apps/**/build/ +tests/e2e/test-apps/**/.next/ +tests/e2e/test-apps/**/.nuxt/ +tests/e2e/test-apps/**/.angular/ +tests/e2e/test-apps/**/node_modules/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7a2c785c..5911a56f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "zod": "^3.22.4" }, "devDependencies": { + "@playwright/test": "^1.56.1", "@rollup/plugin-commonjs": "^25.0.0", "@rollup/plugin-json": "^6.0.0", "@rollup/plugin-node-resolve": "^15.2.0", @@ -2532,6 +2533,22 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz", + "integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.56.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", @@ -10606,6 +10623,53 @@ "node": ">=8" } }, + "node_modules/playwright": { + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", + "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.56.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", + "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", diff --git a/package.json b/package.json index 9e498765..b5766b60 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,9 @@ "test:unit": "vitest tests/unit", "test:watch": "vitest --watch", "test:ui": "vitest --ui", - "test:coverage": "vitest --coverage" + "test:coverage": "vitest --coverage", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui" }, "dependencies": { "@opentelemetry/sdk-logs": "^0.204.0", @@ -48,6 +50,7 @@ "zod": "^3.22.4" }, "devDependencies": { + "@playwright/test": "^1.56.1", "@rollup/plugin-commonjs": "^25.0.0", "@rollup/plugin-json": "^6.0.0", "@rollup/plugin-node-resolve": "^15.2.0", diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 00000000..11e00140 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,128 @@ +import { defineConfig, devices } from '@playwright/test'; +/** + * refer https://playwright.dev/docs/test-configuration + */ +export default defineConfig({ + testDir: './tests/e2e', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code */ + forbidOnly: !!process.env.CI, + /* Retry flaky tests on CI (E2E tests run at release time only) */ + retries: process.env.CI ? 2 : 0, + /* undefined = auto-detect CPU cores (8 cores for for local machine = 8 parallel tests locally, 2 cores = 2 parallel tests in CI in github actions) */ + workers: undefined, + /* Reporter to use */ + reporter: [ + ['html', { outputFolder: 'playwright-report' }], + ['list'], + ['junit', { outputFile: 'test-results/junit.xml' }] + ], + + /* Shared settings for all the projects below */ + use: { + /* Base URL to use in actions like `await page.goto('/')` */ + baseURL: 'http://localhost:3000', + /* Collect trace when retrying the failed test */ + trace: 'on-first-retry', + /* Screenshot on failure */ + screenshot: 'only-on-failure', + /* Video on failure */ + video: 'retain-on-failure', + }, + + /* Configure projects for major browsers */ + projects: [ + // Module tests - run in Node.js environment + { + name: 'module-tests', + testDir: './tests/e2e/module-tests', + testMatch: '**/*.test.ts', + use: { + // No browser needed for module tests + }, + }, + + // Framework tests - React + { + name: 'react', + testDir: './tests/e2e/framework-tests', + testMatch: '**/react.spec.ts', + use: { + ...devices['Desktop Chrome'], + baseURL: 'http://localhost:3000', + }, + }, + + // Framework tests - Vue + // { + // name: 'vue', + // testDir: './tests/e2e/framework-tests', + // testMatch: '**/vue.spec.ts', + // use: { + // ...devices['Desktop Chrome'], + // baseURL: 'http://localhost:3001', + // }, + // }, + + // // Framework tests - Angular + // { + // name: 'angular', + // testDir: './tests/e2e/framework-tests', + // testMatch: '**/angular.spec.ts', + // use: { + // ...devices['Desktop Chrome'], + // baseURL: 'http://localhost:3002', + // }, + // }, + + // Bundler tests + { + name: 'bundler-tests', + testDir: './tests/e2e/bundler-tests', + testMatch: '**/*.spec.ts', + use: { + // Mix of Node.js and browser tests + }, + }, + + // Cross-browser testing (right now we are only testing Safari and chrome) + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + testMatch: '**/browser-*.spec.ts', + } + ], + + /* Run your local dev servers before starting the tests */ + webServer: [ + { + command: 'cd tests/e2e/test-apps/react-app && npm run dev', + port: 3000, + timeout: 120 * 1000, + reuseExistingServer: !process.env.CI, // Always kill any existing server and start fresh(Ensures clean state) in ci + }, + // TODO: Enable when Vue app is created + // { + // command: 'cd tests/e2e/test-apps/vue-app && npm run dev', + // port: 3001, + // timeout: 120 * 1000, + // reuseExistingServer: !process.env.CI, + // }, + // TODO: Enable when Angular app is created + // { + // command: 'cd tests/e2e/test-apps/angular-app && npm run dev', + // port: 3002, + // timeout: 120 * 1000, + // reuseExistingServer: !process.env.CI, + // }, + ], + + /* Timeout for each test (generous for E2E tests with dev servers) */ + timeout: 60 * 1000, + + /* Timeout for each expect */ + expect: { + timeout: 10000, + }, +}); diff --git a/tests/e2e/TEST-STRATEGY.md b/tests/e2e/TEST-STRATEGY.md new file mode 100644 index 00000000..47894d72 --- /dev/null +++ b/tests/e2e/TEST-STRATEGY.md @@ -0,0 +1,203 @@ +# E2E Testing Strategy + +## Overview + +This document outlines our E2E testing strategy for the UiPath TypeScript SDK, ensuring the SDK works correctly across all consumption scenarios: different module systems, bundlers, frameworks, and environments. + +--- + +## Core Testing Categories + +### 1. Module Testing +**What**: Testing how the SDK loads as a JavaScript/TypeScript module. + +**What we test**: +- ✅ ESM imports (`import { UiPath } from '@uipath/uipath-typescript'`) +- ✅ CommonJS requires (`const { UiPath } = require(...)`) +- ✅ UMD browser globals (`window.UiPathSDK`) +- ✅ TypeScript type definitions +- ✅ Tree-shaking effectiveness +- ✅ Package.json exports configuration + +**Tools**: Vitest (Node.js testing, no browser needed) + +--- + +### 2. Framework Testing +**What**: Testing basic SDK functionality within real application frameworks. + +**Why framework testing?** +While frameworks are built on modules, they add layers that can break our SDK: +- **Different build processes**: React (Vite), Angular (Webpack) process modules differently +- **Different bundler configs**: Framework CLIs have opinionated bundler setups +- **Runtime environments**: SSR runs in Node.js, client runs in browser +- **Build optimizations**: Each framework's build tool may transform our code differently + +Module testing verifies imports work, but doesn't catch: +- Build failures in Create React App's Webpack setup +- Vue CLI's bundle optimization breaking tree-shaking +- Angular's AOT compiler issues with our TypeScript types +- Next.js SSR importing browser-only code + +**What we test**: +- ✅ SDK initializes correctly in each framework +- ✅ Framework's build process completes successfully +- ✅ SDK doesn't break HMR during development +- ✅ SSR doesn't crash (Next.js, Nuxt) + +--- + +### 3. Bundler Testing +**What**: Testing how users' bundlers process our SDK. + +**Why this matters**: +When users install our SDK and build their apps with Webpack, Vite, or Rollup, **their bundlers** process our SDK. Even though we build correctly with Rollup, users' bundlers can: +- Break imports (Webpack picks wrong export format) +- Duplicate code (bundles both ESM + CJS versions) +- Inflate bundle size (failed tree-shaking) +- Cause runtime errors (incorrect module resolution) + +**What we test**: +- ✅ **No duplicate code**: SDK isn't bundled twice (both ESM + CJS versions) +- ✅ **Tree-shaking works**: When user imports only `queues`, other services +- ✅ **Dependencies not duplicated**: If both user's app and our SDK use a dependency (axios), it's only bundled once (not twice) +- ✅ **Correct module resolution**: Bundler picks the right format (ESM vs CJS) +- ✅ **Code splitting works**: Dynamic imports don't break SDK + +*note*: playwright not needed + + +--- + +## Directory Structure + +``` +tests/e2e/ +├── module-tests/ # Pure module format testing +│ ├── esm/ +│ │ ├── import.test.ts +│ │ └── tree-shaking.test.ts +│ ├── commonjs/ +│ │ └── require.test.ts +│ └── umd/ +│ └── browser-global.test.ts +│ +├── framework-tests/ # Test specs for frameworks +│ ├── react.spec.ts +│ ├── vue.spec.ts +│ └── angular.spec.ts +│ +├── test-apps/ # Minimal demo apps +│ ├── react-app/ +│ │ ├── src/App.tsx +│ │ ├── package.json # Only React + SDK +│ │ └── vite.config.ts +│ ├── vue-app/ +│ └── angular-app/ +│ +├── bundler-tests/ # Build analysis +│ ├── webpack.spec.ts # Build & analyze +│ ├── vite.spec.ts # Build & analyze +│ ├── rollup.spec.ts # Build & analyze +│ └── browser-umd.spec.ts # ⚠️ Needs Playwright +│ +├── playwright.config.ts # Single config for all +└── package.json # All test dependencies +``` + +--- + +## What We're Testing For + +### Module Loading +```typescript +✓ ESM named imports +✓ ESM default import +✓ CommonJS require +✓ Dynamic import() +✓ UMD global variable +✓ TypeScript types +``` + +### API Surface +```typescript +✓ All classes exported +✓ All interfaces/types exported +✓ No internal APIs exposed +``` + +### Build Artifacts +```typescript +✓ dist/index.mjs (ESM) +✓ dist/index.cjs (CommonJS) +✓ dist/index.umd.js (UMD) +✓ dist/index.d.ts (Types) +✓ Source maps accurate +``` + +### Bundle Quality +```typescript +✓ Tree-shaking removes unused code +✓ No duplicate dependencies +✓ Code splitting works +✓ Minification safe +``` + +### Real-World Problems We Catch + +1. **Tree-shaking Failure**: Entire SDK bundled when user only imports one service + → Result: 300KB bundle instead of 50KB + +2. **Module Resolution Errors**: "Cannot use import statement" + → Result: Runtime crashes + +--- + +## Tool Requirements + +### When to Use Playwright + +| Test Type | Needs Playwright? | Reason | +|-----------|------------------|--------| +| Module Tests (ESM/CJS) | ❌ No | Just Node.js testing | +| Bundler Tests (analysis) | ❌ No | Run bundlers, analyze output | +| UMD in browser | ✅ Yes | Need real browser | +| Framework Apps | ✅ Yes | Testing real apps in browser | +| OAuth flows | ✅ Yes | Browser redirects | +| Tree-shaking | ❌ No | Analyze build output | +| TypeScript types | ❌ No | Use tsc API | + +--- + +## Example Test Scenarios + +### Module Test +```typescript +// ESM import works correctly +import { UiPath } from '@uipath/uipath-typescript'; +const sdk = new UiPath(config); +expect(sdk.queues).toBeDefined(); +``` + +### Framework Test (Playwright) +```typescript +// React: Basic SDK functionality works +test('SDK works in React app', async ({ page }) => { + await page.goto('http://localhost:3000'); + + // SDK initialized + await expect(page.locator('[data-testid="sdk-ready"]')).toBeVisible(); + + // Basic API call works + await page.click('[data-testid="load-queues"]'); + await expect(page.locator('.queues-list')).toBeVisible(); +}); +``` + +### Bundler Test (No Playwright) +```typescript +// Vite: Tree-shaking works +const minimalBuild = await build({ input: 'minimal.js' }); +const minimalSize = minimalBuild.output[0].code.length; +expect(minimalSize).toBeLessThan(50_000); // <50KB +``` \ No newline at end of file diff --git a/tests/e2e/package.json b/tests/e2e/package.json new file mode 100644 index 00000000..32fb226b --- /dev/null +++ b/tests/e2e/package.json @@ -0,0 +1,6 @@ +{ + "name": "@uipath/e2e-tests", + "private": true, + "type": "commonjs", + "description": "End-to-end tests for UiPath TypeScript SDK" + } \ No newline at end of file