From f1c199970eea1c7ce8c11040af913e56fa6d33fd Mon Sep 17 00:00:00 2001 From: prosdev Date: Fri, 21 Nov 2025 01:24:30 -0800 Subject: [PATCH] feat: merge adapters-internal into adapters package - Move FirestoreAdapter from adapters-internal to adapters package - Add @google-cloud/firestore dependency to adapters - Add ./firestore subpath export to adapters package.json - Update all documentation to reference adapters instead of adapters-internal - Remove adapters-internal package directory - Update tsconfig.json and vitest.config.ts to remove adapters-internal references - All adapters (filesystem, Slack, Firestore) now in single package - Reduces maintenance overhead by consolidating packages BREAKING CHANGE: @lytics/playwright-adapters-internal is removed. Use @lytics/playwright-adapters/firestore instead. --- .changeset/merge-adapters-internal.md | 13 + AGENTS.md | 22 +- CLAUDE.md | 9 +- README.md | 13 +- docs/RELEASE_PROCESS.md | 42 --- packages/adapters-internal/CHANGELOG.md | 25 -- packages/adapters-internal/README.md | 259 ------------------ packages/adapters-internal/package.json | 57 ---- packages/adapters-internal/src/index.ts | 29 -- packages/adapters-internal/tsconfig.json | 13 - packages/adapters/README.md | 114 +++++++- packages/adapters/package.json | 13 +- .../src/firestore.test.ts} | 2 +- .../src/firestore/FirestoreAdapter.test.ts | 0 .../src/firestore/FirestoreAdapter.ts | 2 +- .../src/firestore/index.ts | 0 packages/adapters/src/index.test.ts | 12 +- packages/adapters/src/index.ts | 14 +- packages/reporter/README.md | 6 +- pnpm-lock.yaml | 19 +- tsconfig.json | 2 - vitest.config.ts | 5 +- 22 files changed, 186 insertions(+), 485 deletions(-) create mode 100644 .changeset/merge-adapters-internal.md delete mode 100644 packages/adapters-internal/CHANGELOG.md delete mode 100644 packages/adapters-internal/README.md delete mode 100644 packages/adapters-internal/package.json delete mode 100644 packages/adapters-internal/src/index.ts delete mode 100644 packages/adapters-internal/tsconfig.json rename packages/{adapters-internal/src/index.test.ts => adapters/src/firestore.test.ts} (80%) rename packages/{adapters-internal => adapters}/src/firestore/FirestoreAdapter.test.ts (100%) rename packages/{adapters-internal => adapters}/src/firestore/FirestoreAdapter.ts (99%) rename packages/{adapters-internal => adapters}/src/firestore/index.ts (100%) diff --git a/.changeset/merge-adapters-internal.md b/.changeset/merge-adapters-internal.md new file mode 100644 index 0000000..c493ec9 --- /dev/null +++ b/.changeset/merge-adapters-internal.md @@ -0,0 +1,13 @@ +--- +"@lytics/playwright-adapters": major +--- + +Merge `@lytics/playwright-adapters-internal` into `@lytics/playwright-adapters` + +**Breaking Change:** The `@lytics/playwright-adapters-internal` package has been removed and merged into `@lytics/playwright-adapters`. + +**Migration:** +- **Before:** `import { FirestoreAdapter } from '@lytics/playwright-adapters-internal/firestore';` +- **After:** `import { FirestoreAdapter } from '@lytics/playwright-adapters/firestore';` + +All adapters (FilesystemAdapter, SlackAdapter, FirestoreAdapter) are now available in a single package, reducing maintenance overhead. diff --git a/AGENTS.md b/AGENTS.md index d87e53b..672ed8d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,9 +22,8 @@ A TypeScript monorepo providing reusable Playwright testing components built aro packages/ ├── annotations/ # Annotation framework (PUBLIC npm) ├── reporter/ # Core reporter (PUBLIC npm) -├── adapters/ # Generic adapters (PUBLIC npm) -├── adapters-internal/ # Firestore adapter (INTERNAL GitHub Packages) -└── journey/ # Journey tools (INTERNAL GitHub Packages) +├── adapters/ # Storage adapters (PUBLIC npm: filesystem, Slack, Firestore) +└── journey/ # Journey tools (PUBLIC npm) ``` ### Package Details @@ -33,9 +32,8 @@ packages/ |---------|-----------|---------| | `@lytics/playwright-annotations` | Public npm | Annotation framework with validation | | `@lytics/playwright-reporter` | Public npm | Adapter-based reporter | -| `@lytics/playwright-adapters` | Public npm | Generic adapters (filesystem, Slack) | -| `@lytics/playwright-adapters-internal` | GitHub Packages | Firestore adapter (Lytics schema) | -| `@lytics/playwright-journey` | GitHub Packages | Journey-driven test generation | +| `@lytics/playwright-adapters` | Public npm | Storage adapters (filesystem, Slack, Firestore) | +| `@lytics/playwright-journey` | Public npm | Journey-driven test generation | ## Setup Commands @@ -84,7 +82,6 @@ Packages with dependencies must be built in order. Turborepo handles this automa - `@lytics/playwright-annotations` - No dependencies (builds first) - `@lytics/playwright-reporter` - Depends on annotations - `@lytics/playwright-adapters` - Depends on reporter, annotations -- `@lytics/playwright-adapters-internal` - Depends on reporter, annotations - `@lytics/playwright-journey` - Depends on annotations **Critical:** Always run `pnpm build` before `pnpm typecheck` because TypeScript needs the built `.d.ts` files from dependencies. @@ -219,9 +216,6 @@ pnpm changeset - `@lytics/playwright-annotations` - `@lytics/playwright-reporter` - `@lytics/playwright-adapters` - -**Internal packages** → GitHub Packages: -- `@lytics/playwright-adapters-internal` - `@lytics/playwright-journey` ## Package Architecture @@ -247,10 +241,12 @@ Generic storage adapters: - `FilesystemAdapter` - Local JSON storage - `SlackAdapter` - Slack notifications -### Adapters-Internal Package +### Adapters Package -Lytics-specific adapters: -- `FirestoreAdapter` - Firestore with Lytics schema +Storage adapters: +- `FilesystemAdapter` - Local JSON storage +- `SlackAdapter` - Slack notifications +- `FirestoreAdapter` - Google Cloud Firestore storage ### Journey Package diff --git a/CLAUDE.md b/CLAUDE.md index 28ac200..8297bbf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -119,16 +119,13 @@ journey (depends on: annotations) - Provides a pluggable architecture for different storage backends via adapters - Collects and processes test results -3. **@lytics/playwright-adapters**: Generic storage adapters for the reporter. +3. **@lytics/playwright-adapters**: Storage adapters for the reporter. - Filesystem adapter for storing results locally - Slack adapter for notifications + - Firestore adapter for Google Cloud Firestore - Implements common interfaces for storage backends -4. **@lytics/playwright-adapters-internal**: Internal adapters specific to Lytics/Contentstack. - - Firestore adapter with Lytics schema - - Internal implementation details - -5. **@lytics/playwright-journey**: Journey-driven test generation. +4. **@lytics/playwright-journey**: Journey-driven test generation. - Higher-level abstractions for journey-based testing - Connects tests to user journeys and business requirements diff --git a/README.md b/README.md index f2feab5..ac593a9 100644 --- a/README.md +++ b/README.md @@ -16,16 +16,13 @@ Open-source, generic infrastructure for any Playwright project: |---------|-------------|---------| | [`@lytics/playwright-annotations`](./packages/annotations) | Annotation framework with validation & ESLint | ![npm](https://img.shields.io/npm/v/@lytics/playwright-annotations) | | [`@lytics/playwright-reporter`](./packages/reporter) | Adapter-based reporter with pluggable storage | ![npm](https://img.shields.io/npm/v/@lytics/playwright-reporter) | -| [`@lytics/playwright-adapters`](./packages/adapters) | Generic storage adapters (filesystem, Slack) | ![npm](https://img.shields.io/npm/v/@lytics/playwright-adapters) | +| [`@lytics/playwright-adapters`](./packages/adapters) | Storage adapters (filesystem, Slack, Firestore) | ![npm](https://img.shields.io/npm/v/@lytics/playwright-adapters) | -### Internal Packages (GitHub Packages) +### Contentstack-Specific Packages -Lytics/Contentstack-specific workflows: - -| Package | Description | -|---------|-------------| -| [`@lytics/playwright-adapters-internal`](./packages/adapters-internal) | Firestore adapter with Lytics schema | -| [`@lytics/playwright-journey`](./packages/journey) | Journey-driven test generation | +| Package | Description | Version | +|---------|-------------|---------| +| [`@lytics/playwright-journey`](./packages/journey) | Journey-driven test generation | ![npm](https://img.shields.io/npm/v/@lytics/playwright-journey) | ## 🚀 Quick Start diff --git a/docs/RELEASE_PROCESS.md b/docs/RELEASE_PROCESS.md index e80718d..22629f9 100644 --- a/docs/RELEASE_PROCESS.md +++ b/docs/RELEASE_PROCESS.md @@ -61,48 +61,6 @@ Packages without `publishConfig` are published to npm: } ``` -### Internal Packages (GitHub Packages) - -Packages with `registry` configured are published to GitHub Packages: - -```json -{ - "name": "@lytics/playwright-adapters-internal", - "publishConfig": { - "registry": "https://npm.pkg.github.com", - "access": "restricted" - } -} -``` - -## Installing Internal Packages - -To install packages from GitHub Packages, configure npm authentication: - -### Option 1: Using GITHUB_TOKEN (CI/CD) - -```bash -# In your CI/CD environment -echo "@lytics:registry=https://npm.pkg.github.com" >> .npmrc -echo "//npm.pkg.github.com/:_authToken=$GITHUB_TOKEN" >> .npmrc -``` - -### Option 2: Using Personal Access Token (Local) - -Create `.npmrc` in your project: - -``` -@lytics:registry=https://npm.pkg.github.com -//npm.pkg.github.com/:_authToken=YOUR_PERSONAL_ACCESS_TOKEN -``` - -**Note:** Add `.npmrc` to `.gitignore` if it contains tokens! - -Then install: - -```bash -npm install @lytics/playwright-adapters-internal -``` ## Versioning Guidelines diff --git a/packages/adapters-internal/CHANGELOG.md b/packages/adapters-internal/CHANGELOG.md deleted file mode 100644 index cc3e25f..0000000 --- a/packages/adapters-internal/CHANGELOG.md +++ /dev/null @@ -1,25 +0,0 @@ -# @lytics/playwright-adapters-internal - -## 0.3.0 - -### Minor Changes - -- 7237ab4: Publish internal packages as public on npm and hide internal schema details - - - Make FirestoreAdapter collections required (no defaults) to hide internal schema - - Update packages to publish to npm as public instead of GitHub Packages - - Remove GitHub Packages authentication from release workflow - - Update documentation to require users to configure their own collection names - -## 0.2.0 - -### Minor Changes - -- 30970dd: Implement FirestoreAdapter for @lytics/playwright-adapters-internal - - - Add FirestoreAdapter with retry logic and exponential backoff for transient failures - - Support GCP authentication via Application Default Credentials (ADC) or Service Account JSON - - Write to three Firestore collections (test_runs, individual_test_runs, latest_test_cases) - - Add skip conditions for pull requests - - Comprehensive test coverage (27 tests) - - Full README documentation with authentication setup and Firestore schema diff --git a/packages/adapters-internal/README.md b/packages/adapters-internal/README.md deleted file mode 100644 index f45a0d0..0000000 --- a/packages/adapters-internal/README.md +++ /dev/null @@ -1,259 +0,0 @@ -# @lytics/playwright-adapters-internal - -Contentstack-specific storage adapters for the Playwright reporter framework. - -> **Note:** This package is designed for Contentstack's internal use. While it's published publicly, it requires you to configure your own Firestore schema and collection names. The implementation details are generic, but you'll need to adapt it to your own infrastructure. - -## Installation - -```bash -npm install @lytics/playwright-adapters-internal -# or -pnpm add @lytics/playwright-adapters-internal -``` - -## Adapters - -### FirestoreAdapter - -Writes test results to Google Cloud Firestore. Supports automatic retry with exponential backoff for transient failures. - -**Features:** -- Writes to three configurable Firestore collections (you define the collection names) -- Automatic retry with exponential backoff for transient failures -- GCP authentication via Application Default Credentials (ADC) or Service Account JSON -- Optional skip conditions (e.g., skip PR runs) -- Non-blocking error handling (allows other adapters to continue) -- Schema-agnostic (you configure your own collection names and structure) - -**Example:** - -```typescript -import { CoreReporter } from '@lytics/playwright-reporter'; -import { FirestoreAdapter } from '@lytics/playwright-adapters-internal/firestore'; - -export default { - reporter: [ - ['list'], - [ - new CoreReporter({ - adapters: [ - new FirestoreAdapter({ - projectId: 'my-gcp-project', - credentials: process.env.GOOGLE_APPLICATION_CREDENTIALS_JSON, - // Configure your own collection names to match your Firestore schema - collections: { - testRuns: 'your_test_runs_collection', - testCases: 'your_test_cases_collection', - latestTestCases: 'your_latest_test_cases_collection', - }, - skipConditions: { - skipPullRequests: true, - }, - }), - ], - }), - ], - ], -}; -``` - -**Configuration:** - -```typescript -interface FirestoreAdapterConfig { - /** GCP project ID (required) */ - projectId: string; - /** Service account credentials (JSON string or object). If not provided, uses Application Default Credentials */ - credentials?: string | Record; - /** Collection names for different data types (required - configure your own schema) */ - collections: { - /** Collection for test run summaries */ - testRuns: string; - /** Collection for individual test executions */ - testCases: string; - /** Collection for latest test case status */ - latestTestCases: string; - }; - /** Optional conditions to skip writes */ - skipConditions?: { - /** Skip writes for pull requests (default: false) */ - skipPullRequests?: boolean; - }; - /** Retry configuration */ - retry?: { - /** Maximum number of retries (default: 3) */ - maxRetries?: number; - /** Initial delay in milliseconds (default: 1000) */ - initialDelayMs?: number; - /** Maximum delay in milliseconds (default: 10000) */ - maxDelayMs?: number; - }; -} -``` - -## Authentication - -The FirestoreAdapter supports two authentication methods: - -### 1. Application Default Credentials (ADC) - Recommended - -When `credentials` is not provided, the adapter uses Application Default Credentials. This is the recommended approach for production environments. - -**Setup:** - -```bash -# Set the GOOGLE_APPLICATION_CREDENTIALS environment variable -export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-key.json - -# Or use gcloud CLI to authenticate -gcloud auth application-default login -``` - -**Usage:** - -```typescript -new FirestoreAdapter({ - projectId: 'my-gcp-project', - // credentials not provided - uses ADC -}); -``` - -### 2. Service Account JSON - -Provide service account credentials directly as a JSON string or object. - -**Usage:** - -```typescript -// From environment variable (JSON string) -new FirestoreAdapter({ - projectId: 'my-gcp-project', - credentials: process.env.GOOGLE_APPLICATION_CREDENTIALS_JSON, -}); - -// Or as an object -new FirestoreAdapter({ - projectId: 'my-gcp-project', - credentials: { - type: 'service_account', - project_id: 'my-gcp-project', - private_key_id: '...', - private_key: '...', - client_email: '...', - // ... other fields - }, -}); -``` - -## Firestore Schema - -The adapter writes to three collections that you configure. The data structure follows the `CoreTestResult` and `CoreTestRun` types from `@lytics/playwright-reporter`. Example schema: - -### Test Runs Collection - -Test run summaries with aggregate statistics (configure your own collection name). - -```typescript -{ - runId: string; - timestamp: string; // ISO 8601 - overallStatus: 'passed' | 'failed' | 'timedOut' | 'skipped' | 'cancelled' | 'interrupted' | 'unknown'; - totalTests: number; - totalExecutions: number; - passed: number; - failed: number; - skipped: number; - durationMs: number; - passRate: number; - averageTestDuration: number; - slowestTestDuration: number; - flakyTests: number; - // ... environment variables spread here -} -``` - -### Test Cases Collection - -Individual test execution records (one document per test execution - configure your own collection name). - -```typescript -{ - testCaseId: string; - journeyId: string; - title: string; - status: 'passed' | 'failed' | 'timedOut' | 'skipped' | 'cancelled' | 'interrupted' | 'unknown'; - projectName: string; - errorMessage: Record | null; - testSuiteName: string; - durationMs: number; - runTimestamp: Timestamp; // Firestore server timestamp - buildId: string; - reportLink: string | null; -} -``` - -### Latest Test Cases Collection - -Latest status for each test case (updated on each run, merged - configure your own collection name). - -```typescript -{ - journeyId: string; - title: string; - testSuiteName: string; - lastRunResult: 'passed' | 'failed' | 'timedOut' | 'skipped' | 'cancelled' | 'interrupted' | 'unknown'; - lastRunTimestamp: Timestamp; // Firestore server timestamp - lastRunDurationMs: number; - lastRunBuildId: string; - lastRunReportLink: string | null; - lastRunError: Record | null; - projectName: string; - automationStatus: 'done'; -} -``` - -**Document ID:** Uses `testCaseId` as the document ID for easy lookups. - -> **Note:** The exact schema and field names are determined by the `CoreTestResult` and `CoreTestRun` types. You can customize the collection names to match your existing Firestore structure. - -## Retry Logic - -The adapter automatically retries failed operations with exponential backoff. Retries are only attempted for transient errors (network issues, rate limits, etc.). Non-retryable errors (authentication, invalid data) fail immediately. - -**Retryable Errors:** -- Network timeouts -- Rate limit errors -- Temporary service unavailability - -**Non-Retryable Errors:** -- Permission denied (authentication) -- Invalid argument (data validation) -- Not found (configuration issues) - -## Skip Conditions - -The adapter can skip writes based on environment conditions: - -```typescript -// Skip writes for pull requests -new FirestoreAdapter({ - projectId: 'my-gcp-project', - skipConditions: { - skipPullRequests: true, // Skips when TRIGGER_TYPE=pull_request - }, -}); -``` - -## Error Handling - -The adapter uses non-blocking error handling. If a write operation fails after all retries, the error is logged but not thrown. This allows other adapters in the chain to continue processing. - -## Environment Variables - -- `TRIGGER_TYPE`: Used by `skipPullRequests` condition. Set to `'pull_request'` to skip writes. - -## License - -MIT - While this package is designed for Contentstack's internal use, it's published publicly for transparency. You'll need to configure your own Firestore schema and collection names to use it. - diff --git a/packages/adapters-internal/package.json b/packages/adapters-internal/package.json deleted file mode 100644 index 4ef66e7..0000000 --- a/packages/adapters-internal/package.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "name": "@lytics/playwright-adapters-internal", - "version": "0.3.0", - "description": "Contentstack-specific storage adapters (Firestore with Contentstack schema)", - "private": false, - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "require": "./dist/index.js" - }, - "./firestore": { - "types": "./dist/firestore/index.d.ts", - "import": "./dist/firestore/index.js", - "require": "./dist/firestore/index.js" - } - }, - "files": [ - "dist", - "README.md" - ], - "scripts": { - "build": "tsc", - "dev": "tsc --watch", - "clean": "rm -rf dist", - "typecheck": "tsc --noEmit", - "lint": "biome lint ./src", - "format": "biome format --write ./src", - "test": "vitest run", - "test:watch": "vitest" - }, - "keywords": [ - "playwright", - "adapter", - "firestore", - "lytics", - "internal" - ], - "repository": { - "type": "git", - "url": "https://github.com/lytics/playwright-core.git", - "directory": "packages/adapters-internal" - }, - "publishConfig": { - "access": "public" - }, - "dependencies": { - "@lytics/playwright-annotations": "^0.1.0", - "@lytics/playwright-reporter": "^0.1.0", - "@google-cloud/firestore": "^7.0.0" - }, - "devDependencies": { - "typescript": "^5.3.3" - } -} diff --git a/packages/adapters-internal/src/index.ts b/packages/adapters-internal/src/index.ts deleted file mode 100644 index b8293d3..0000000 --- a/packages/adapters-internal/src/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @lytics/playwright-adapters-internal - * - * Contentstack-specific storage adapters for Playwright reporter. - * - * ## Adapters - * - * - **FirestoreAdapter** - Write test results to Google Cloud Firestore - * - * @example - * ```typescript - * import { CoreReporter } from '@lytics/playwright-reporter'; - * import { FirestoreAdapter } from '@lytics/playwright-adapters-internal/firestore'; - * - * const reporter = new CoreReporter({ - * adapters: [ - * new FirestoreAdapter({ - * projectId: 'my-gcp-project', - * credentials: process.env.GOOGLE_APPLICATION_CREDENTIALS_JSON, - * }) - * ] - * }); - * ``` - * - * @packageDocumentation - */ - -export type { FirestoreAdapterConfig } from './firestore'; -export { FirestoreAdapter } from './firestore'; diff --git a/packages/adapters-internal/tsconfig.json b/packages/adapters-internal/tsconfig.json deleted file mode 100644 index 8229e20..0000000 --- a/packages/adapters-internal/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./dist", - "rootDir": "./src" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "**/*.test.ts"], - "references": [ - { "path": "../annotations" }, - { "path": "../reporter" } - ] -} \ No newline at end of file diff --git a/packages/adapters/README.md b/packages/adapters/README.md index ddb56dd..f9cd79b 100644 --- a/packages/adapters/README.md +++ b/packages/adapters/README.md @@ -1,6 +1,6 @@ # @lytics/playwright-adapters -Generic storage adapters for the Playwright reporter framework. +Storage adapters for the Playwright reporter framework (filesystem, Slack, Firestore). ## Installation @@ -150,6 +150,118 @@ The adapter automatically reads from environment variables if not provided in co **Note:** This adapter requires `@lytics/playwright-slack` and `@lytics/slack-client` to be installed as dependencies. +### FirestoreAdapter + +Writes test results to Google Cloud Firestore. Supports automatic retry with exponential backoff for transient failures. + +**Features:** +- Writes to three configurable Firestore collections (you define the collection names) +- Automatic retry with exponential backoff for transient failures +- GCP authentication via Application Default Credentials (ADC) or Service Account JSON +- Optional skip conditions (e.g., skip PR runs) +- Non-blocking error handling (allows other adapters to continue) +- Schema-agnostic (you configure your own collection names and structure) + +**Example:** + +```typescript +import { CoreReporter } from '@lytics/playwright-reporter'; +import { FirestoreAdapter } from '@lytics/playwright-adapters/firestore'; + +export default { + reporter: [ + ['list'], + [ + new CoreReporter({ + adapters: [ + new FirestoreAdapter({ + projectId: 'my-gcp-project', + credentials: process.env.GOOGLE_APPLICATION_CREDENTIALS_JSON, + // Configure your own collection names to match your Firestore schema + collections: { + testRuns: 'your_test_runs_collection', + testCases: 'your_test_cases_collection', + latestTestCases: 'your_latest_test_cases_collection', + }, + skipConditions: { + skipPullRequests: true, + }, + }), + ], + }), + ], + ], +}; +``` + +**Configuration:** + +```typescript +interface FirestoreAdapterConfig { + /** GCP project ID (required) */ + projectId: string; + /** Service account credentials (JSON string or object). If not provided, uses Application Default Credentials */ + credentials?: string | Record; + /** Collection names for different data types (required - configure your own schema) */ + collections: { + /** Collection for test run summaries */ + testRuns: string; + /** Collection for individual test executions */ + testCases: string; + /** Collection for latest test case status */ + latestTestCases: string; + }; + /** Optional conditions to skip writes */ + skipConditions?: { + /** Skip writes for pull requests (default: false) */ + skipPullRequests?: boolean; + }; + /** Retry configuration */ + retry?: { + /** Maximum number of retries (default: 3) */ + maxRetries?: number; + /** Initial delay in milliseconds (default: 1000) */ + initialDelayMs?: number; + /** Maximum delay in milliseconds (default: 10000) */ + maxDelayMs?: number; + }; +} +``` + +**Authentication:** + +The FirestoreAdapter supports two authentication methods: + +1. **Application Default Credentials (ADC)** - Recommended: When `credentials` is not provided, the adapter uses Application Default Credentials. + +```bash +# Set the GOOGLE_APPLICATION_CREDENTIALS environment variable +export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-key.json + +# Or use gcloud CLI to authenticate +gcloud auth application-default login +``` + +2. **Service Account JSON**: Provide service account credentials directly as a JSON string or object. + +```typescript +// From environment variable (JSON string) +new FirestoreAdapter({ + projectId: 'my-gcp-project', + credentials: process.env.GOOGLE_APPLICATION_CREDENTIALS_JSON, +}); +``` + +**Retry Logic:** + +The adapter automatically retries failed operations with exponential backoff. Retries are only attempted for transient errors (network issues, rate limits, etc.). Non-retryable errors (authentication, invalid data) fail immediately. + +**Environment Variables:** + +- `TRIGGER_TYPE`: Used by `skipPullRequests` condition. Set to `'pull_request'` to skip writes. + +**Note:** This adapter requires `@google-cloud/firestore` to be installed as a dependency. + ## Creating Custom Adapters Adapters implement the `ResultAdapter` interface: diff --git a/packages/adapters/package.json b/packages/adapters/package.json index 3bfe107..3196e7f 100644 --- a/packages/adapters/package.json +++ b/packages/adapters/package.json @@ -1,7 +1,7 @@ { "name": "@lytics/playwright-adapters", "version": "0.2.0", - "description": "Generic storage adapters for Playwright reporter (filesystem, Slack)", + "description": "Storage adapters for Playwright reporter (filesystem, Slack, Firestore)", "private": false, "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -20,6 +20,11 @@ "types": "./dist/slack/index.d.ts", "import": "./dist/slack/index.js", "require": "./dist/slack/index.js" + }, + "./firestore": { + "types": "./dist/firestore/index.d.ts", + "import": "./dist/firestore/index.js", + "require": "./dist/firestore/index.js" } }, "files": [ @@ -41,7 +46,8 @@ "adapter", "storage", "filesystem", - "slack" + "slack", + "firestore" ], "repository": { "type": "git", @@ -55,7 +61,8 @@ "@lytics/playwright-annotations": "^0.1.0", "@lytics/playwright-reporter": "^0.1.0", "@lytics/playwright-slack": "^0.3.1", - "@lytics/slack-client": "^1.1.2" + "@lytics/slack-client": "^1.1.2", + "@google-cloud/firestore": "^7.0.0" }, "devDependencies": { "@types/node": "^22.0.0", diff --git a/packages/adapters-internal/src/index.test.ts b/packages/adapters/src/firestore.test.ts similarity index 80% rename from packages/adapters-internal/src/index.test.ts rename to packages/adapters/src/firestore.test.ts index 8bbbe92..bacd8d9 100644 --- a/packages/adapters-internal/src/index.test.ts +++ b/packages/adapters/src/firestore.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; import { FirestoreAdapter } from './index'; -describe('@lytics/playwright-adapters-internal', () => { +describe('@lytics/playwright-adapters firestore', () => { it('should export FirestoreAdapter', () => { expect(FirestoreAdapter).toBeDefined(); expect(typeof FirestoreAdapter).toBe('function'); diff --git a/packages/adapters-internal/src/firestore/FirestoreAdapter.test.ts b/packages/adapters/src/firestore/FirestoreAdapter.test.ts similarity index 100% rename from packages/adapters-internal/src/firestore/FirestoreAdapter.test.ts rename to packages/adapters/src/firestore/FirestoreAdapter.test.ts diff --git a/packages/adapters-internal/src/firestore/FirestoreAdapter.ts b/packages/adapters/src/firestore/FirestoreAdapter.ts similarity index 99% rename from packages/adapters-internal/src/firestore/FirestoreAdapter.ts rename to packages/adapters/src/firestore/FirestoreAdapter.ts index ee5fe71..f965646 100644 --- a/packages/adapters-internal/src/firestore/FirestoreAdapter.ts +++ b/packages/adapters/src/firestore/FirestoreAdapter.ts @@ -48,7 +48,7 @@ export interface FirestoreAdapterConfig { * * @example * ```typescript - * import { FirestoreAdapter } from '@lytics/playwright-adapters-internal/firestore'; + * import { FirestoreAdapter } from '@lytics/playwright-adapters/firestore'; * * const adapter = new FirestoreAdapter({ * projectId: 'my-gcp-project', diff --git a/packages/adapters-internal/src/firestore/index.ts b/packages/adapters/src/firestore/index.ts similarity index 100% rename from packages/adapters-internal/src/firestore/index.ts rename to packages/adapters/src/firestore/index.ts diff --git a/packages/adapters/src/index.test.ts b/packages/adapters/src/index.test.ts index d3d76a6..2cbe339 100644 --- a/packages/adapters/src/index.test.ts +++ b/packages/adapters/src/index.test.ts @@ -1,9 +1,19 @@ import { describe, expect, it } from 'vitest'; -import { FilesystemAdapter } from './index'; +import { FilesystemAdapter, FirestoreAdapter, SlackAdapter } from './index'; describe('@lytics/playwright-adapters', () => { it('should export FilesystemAdapter', () => { expect(FilesystemAdapter).toBeDefined(); expect(typeof FilesystemAdapter).toBe('function'); }); + + it('should export SlackAdapter', () => { + expect(SlackAdapter).toBeDefined(); + expect(typeof SlackAdapter).toBe('function'); + }); + + it('should export FirestoreAdapter', () => { + expect(FirestoreAdapter).toBeDefined(); + expect(typeof FirestoreAdapter).toBe('function'); + }); }); diff --git a/packages/adapters/src/index.ts b/packages/adapters/src/index.ts index 2d0c9e5..6dce03c 100644 --- a/packages/adapters/src/index.ts +++ b/packages/adapters/src/index.ts @@ -7,17 +7,27 @@ * * - **FilesystemAdapter** - Write test results to JSON files * - **SlackAdapter** - Send test summaries to Slack channels + * - **FirestoreAdapter** - Write test results to Google Cloud Firestore * * @example * ```typescript * import { CoreReporter } from '@lytics/playwright-reporter'; * import { FilesystemAdapter } from '@lytics/playwright-adapters/filesystem'; * import { SlackAdapter } from '@lytics/playwright-adapters/slack'; + * import { FirestoreAdapter } from '@lytics/playwright-adapters/firestore'; * * const reporter = new CoreReporter({ * adapters: [ * new FilesystemAdapter({ outputDir: './test-results' }), - * new SlackAdapter({ webhookUrl: process.env.SLACK_WEBHOOK_URL }) + * new SlackAdapter({ webhookUrl: process.env.SLACK_WEBHOOK_URL }), + * new FirestoreAdapter({ + * projectId: 'my-gcp-project', + * collections: { + * testRuns: 'test_runs', + * testCases: 'test_cases', + * latestTestCases: 'latest_test_cases' + * } + * }) * ] * }); * ``` @@ -28,5 +38,7 @@ export type { FilesystemAdapterConfig } from './filesystem'; // Re-export adapters export { FilesystemAdapter } from './filesystem'; +export type { FirestoreAdapterConfig } from './firestore'; +export { FirestoreAdapter } from './firestore'; export type { SlackAdapterConfig } from './slack'; export { SlackAdapter } from './slack'; diff --git a/packages/reporter/README.md b/packages/reporter/README.md index bfb8239..7804793 100644 --- a/packages/reporter/README.md +++ b/packages/reporter/README.md @@ -327,8 +327,7 @@ The reporter uses these environment variables (can be customized via `environmen See these packages for ready-to-use adapters: -- [`@lytics/playwright-adapters`](../adapters) - Filesystem, Slack -- [`@lytics/playwright-adapters-internal`](../adapters-internal) - Firestore (Lytics-specific) +- [`@lytics/playwright-adapters`](../adapters) - Filesystem, Slack, Firestore ## Troubleshooting @@ -364,6 +363,5 @@ MIT ## Related Packages - [`@lytics/playwright-annotations`](../annotations) - Annotation framework -- [`@lytics/playwright-adapters`](../adapters) - Generic storage adapters -- [`@lytics/playwright-adapters-internal`](../adapters-internal) - Internal adapters +- [`@lytics/playwright-adapters`](../adapters) - Storage adapters (Filesystem, Slack, Firestore) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9a486a..bd2e622 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: packages/adapters: dependencies: + '@google-cloud/firestore': + specifier: ^7.0.0 + version: 7.11.6 '@lytics/playwright-annotations': specifier: ^0.1.0 version: link:../annotations @@ -61,22 +64,6 @@ importers: specifier: ^5.3.3 version: 5.9.3 - packages/adapters-internal: - dependencies: - '@google-cloud/firestore': - specifier: ^7.0.0 - version: 7.11.6 - '@lytics/playwright-annotations': - specifier: ^0.1.0 - version: link:../annotations - '@lytics/playwright-reporter': - specifier: ^0.1.0 - version: link:../reporter - devDependencies: - typescript: - specifier: ^5.3.3 - version: 5.9.3 - packages/annotations: devDependencies: '@playwright/test': diff --git a/tsconfig.json b/tsconfig.json index 1ef35a7..77b7994 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,8 +19,6 @@ "@lytics/playwright-reporter/*": ["packages/reporter/src/*"], "@lytics/playwright-adapters": ["packages/adapters/src"], "@lytics/playwright-adapters/*": ["packages/adapters/src/*"], - "@lytics/playwright-adapters-internal": ["packages/adapters-internal/src"], - "@lytics/playwright-adapters-internal/*": ["packages/adapters-internal/src/*"], "@lytics/playwright-journey": ["packages/journey/src"], "@lytics/playwright-journey/*": ["packages/journey/src/*"] } diff --git a/vitest.config.ts b/vitest.config.ts index 674c030..df57d31 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,5 +1,5 @@ -import { defineConfig } from 'vitest/config'; import { resolve } from 'path'; +import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { @@ -17,8 +17,7 @@ export default defineConfig({ '@lytics/playwright-annotations': resolve(__dirname, 'packages/annotations/src'), '@lytics/playwright-reporter': resolve(__dirname, 'packages/reporter/src'), '@lytics/playwright-adapters': resolve(__dirname, 'packages/adapters/src'), - '@lytics/playwright-adapters-internal': resolve(__dirname, 'packages/adapters-internal/src'), '@lytics/playwright-journey': resolve(__dirname, 'packages/journey/src'), }, }, -}); \ No newline at end of file +});