Introduction: Why Playwright Architecture Matters for Large Projects
A Playwright test suite can start with a simple structure:
tests/
└── login.spec.ts
That works when a project has 10 or 20 tests.
It becomes difficult when the same automation suite grows to:
- Hundreds or thousands of tests
- Multiple applications
- Multiple teams
- Multiple browsers
- Several environments
- API and UI tests
- Parallel execution
- CI/CD pipelines
- Test sharding
- Complex authentication
- Large test-data requirements
At that point, adding more test files is not an architecture.
You need a Playwright test architecture for large projects that controls dependencies, isolates tests, reduces duplication, makes failures easy to debug, and allows the framework to scale without becoming difficult to maintain.
Playwright provides several capabilities that support this architecture, including projects, fixtures, parallel workers, sharding, authentication state, reporters, browser/device configuration, and CI integration. Projects can represent different browsers, devices, environments, or logical test groups.
This guide presents a practical Playwright enterprise framework architecture using TypeScript.
What Is Playwright Test Architecture?
Playwright Test Architecture is the organization of test code, page objects, fixtures, API clients, authentication, test data, configuration, utilities, reporting, and CI/CD into clearly defined layers.
A scalable architecture looks like this:
Playwright Test Runner
|
Test Specifications
|
+————–+————–+
| | |
Pages Fixtures APIs
| | |
+————–+————–+
|
Shared Utilities
|
+————-+————-+
| | |
Test Data Config Auth
| | |
+————-+————-+
|
Playwright Projects
|
+————–+————–+
| | |
Chromium Firefox WebKit
|
CI/CD + Sharding
|
Reports + Artifacts
The goal is separation of responsibility.
A test should describe business behavior.
It should not contain:
- Authentication implementation
- Database connection code
- Environment parsing
- API client implementation
- Browser configuration
- Screenshot upload logic
- Repeated selectors
That separation is the foundation of a scalable Playwright Automation Framework.
Small Project vs Enterprise Playwright Architecture
| Area | Small Project | Large Project |
| Tests | 10–50 | Hundreds/thousands |
| Structure | Tests + config | Layered architecture |
| Authentication | Inline login | Auth fixtures/storage state |
| Data | Hard-coded | Factories/builders/API/database |
| Pages | Optional POM | POM + reusable components |
| APIs | Direct requests | API client layer |
| Configuration | One environment | Environment/project configuration |
| Execution | Local | Parallel + sharding |
| Reporting | HTML | HTML + JSON/JUnit/custom |
| CI/CD | Basic | Multi-stage pipeline |
| Debugging | Screenshots | Traces + artifacts + logs |
| Teams | One | Multiple teams |
The key difference is not simply the number of files.
It is the number of responsibilities and dependencies the framework must manage.
Key Principles of Scalable Playwright Framework Design
A good Playwright framework design for large projects should follow these principles.
1. Single responsibility
Each layer should have one primary responsibility.
2. Reusability
Common behavior belongs in fixtures, utilities, page objects, or API clients.
3. Test isolation
Tests should not depend on another test’s execution order or data.
4. Configuration-driven execution
Browser, environment, retries, workers, and URLs should be configurable.
5. Deterministic test data
Tests should create or control the data they need.
6. Parallel-safe design
Tests must work when multiple workers execute simultaneously.
7. CI-first architecture
The framework should be designed for CI rather than treating CI as an afterthought.
8. Observability
Failures should provide enough information to diagnose the problem.
9. Controlled dependencies
Tests should not unnecessarily depend on external systems.
10. Easy onboarding
A new SDET should understand where new tests, pages, fixtures, and data belong.
Recommended Playwright Project Structure
A practical Playwright Project Structure is:
playwright-framework/
│
├── tests/
│ ├── auth/
│ ├── checkout/
│ ├── orders/
│ ├── products/
│ └── users/
│
├── pages/
│ ├── LoginPage.ts
│ ├── DashboardPage.ts
│ ├── ProductPage.ts
│ └── CheckoutPage.ts
│
├── components/
│ ├── Header.ts
│ ├── Navigation.ts
│ └── ProductCard.ts
│
├── fixtures/
│ ├── test.ts
│ ├── auth.fixture.ts
│ └── api.fixture.ts
│
├── api/
│ ├── UserApi.ts
│ ├── ProductApi.ts
│ └── OrderApi.ts
│
├── auth/
│ ├── admin.json
│ └── user.json
│
├── test-data/
│ ├── users.ts
│ ├── products.ts
│ └── orders.ts
│
├── utils/
│ ├── dates.ts
│ ├── environment.ts
│ └── random.ts
│
├── config/
│ └── environments.ts
│
├── reports/
│
├── playwright.config.ts
├── package.json
└── tsconfig.json
Responsibility of Each Directory
| Directory | Responsibility |
| tests | Business-level test scenarios |
| pages | Page Object Model classes |
| components | Reusable UI components |
| fixtures | Dependency injection and setup |
| api | API clients |
| auth | Authentication state |
| test-data | Controlled test data |
| utils | Generic utilities |
| config | Environment/framework configuration |
| reports | Generated reporting output |
This structure prevents a common failure pattern where everything ends up inside tests/.
Playwright Configuration Architecture
The configuration is the central execution contract.
Example:
import { defineConfig, devices } from ‘@playwright/test’;
const isCI = !!process.env.CI;
export default defineConfig({
testDir: ‘./tests’,
timeout: 30_000,
expect: {
timeout: 5_000
},
fullyParallel: true,
forbidOnly: isCI,
retries: isCI ? 2 : 0,
workers: isCI ? 2 : undefined,
reporter: isCI
? [[‘blob’]]
: [[‘list’], [‘html’, { open: ‘never’ }]],
outputDir: ‘./test-results’,
use: {
baseURL:
process.env.BASE_URL ?? ‘http://localhost:3000’,
trace: ‘on-first-retry’,
screenshot: ‘only-on-failure’,
video: ‘retain-on-failure’
},
projects: [
{
name: ‘chromium’,
use: {
…devices[‘Desktop Chrome’]
}
},
{
name: ‘firefox’,
use: {
…devices[‘Desktop Firefox’]
}
},
{
name: ‘webkit’,
use: {
…devices[‘Desktop Safari’]
}
}
]
});
Playwright configuration supports fullyParallel, workers, projects, retries, reporter, outputDir, use, and other settings needed for large suites.
One important architectural decision is to avoid putting every option directly into use.
For example, workers and fullyParallel are test configuration options, not use options.
Page Object Model and Reusable Components
The Page Object Model remains useful when implemented carefully.
Example:
import { expect, Locator, Page } from ‘@playwright/test’;
export class LoginPage {
readonly username: Locator;
readonly password: Locator;
readonly loginButton: Locator;
constructor(private readonly page: Page) {
this.username = page.getByLabel(‘Username’);
this.password = page.getByLabel(‘Password’);
this.loginButton = page.getByRole(‘button’, {
name: ‘Login’
});
}
async goto() {
await this.page.goto(‘/login’);
}
async login(username: string, password: string) {
await this.username.fill(username);
await this.password.fill(password);
await this.loginButton.click();
}
async expectLoggedIn() {
await expect(
this.page.getByRole(‘heading’, {
name: ‘Dashboard’
})
).toBeVisible();
}
}
Test:
import { test } from ‘@playwright/test’;
import { LoginPage } from ‘../pages/LoginPage’;
test(‘user can log in’, async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login(
‘testuser’,
‘password’
);
await loginPage.expectLoggedIn();
});
Do not create a giant Page Object
Avoid:
ApplicationPage.ts
containing hundreds of methods.
Instead:
pages/
components/
flows/
A reusable header should be a component.
A checkout process may be a business flow.
A page object should represent meaningful interaction with a page or page-level responsibility.
Custom Fixtures and Dependency Management
Fixtures are one of the strongest features for Playwright TypeScript framework architecture.
Playwright fixtures provide reusable setup and teardown and can also express dependencies between test resources.
Create:
fixtures/test.ts
import { test as base, expect } from ‘@playwright/test’;
import { LoginPage } from ‘../pages/LoginPage’;
type Fixtures = {
loginPage: LoginPage;
};
export const test = base.extend<Fixtures>({
loginPage: async ({ page }, use) => {
await use(new LoginPage(page));
}
});
export { expect };
Now:
import { test, expect } from ‘../fixtures/test’;
test(‘login test’, async ({ loginPage }) => {
await loginPage.goto();
await loginPage.login(
‘user’,
‘password’
);
await loginPage.expectLoggedIn();
});
The test no longer needs to construct LoginPage.
This is dependency injection.
Architecture
Test
↓
Fixture
↓
Page Object
↓
Playwright Page
For large frameworks, fixtures can provide:
- Page objects
- API clients
- Authenticated contexts
- Test data
- Database helpers
- Mock services
- Feature flags
Authentication and Multi-User Test Architecture
Repeatedly logging in through the UI wastes time.
Playwright supports reusing authenticated browser state through storageState. The official authentication guidance also describes using one account per parallel worker when tests modify server-side state.
A simple authenticated project:
{
name: ‘authenticated’,
use: {
…devices[‘Desktop Chrome’],
storageState: ‘auth/user.json’
}
}
Then:
test(‘dashboard loads’, async ({ page }) => {
await page.goto(‘/dashboard’);
await expect(
page.getByRole(‘heading’, {
name: ‘Dashboard’
})
).toBeVisible();
});
For parallel tests that modify server-side state, use separate accounts or worker-scoped authentication.
A useful model is:
Worker 1 → user-1
Worker 2 → user-2
Worker 3 → user-3
Worker 4 → user-4
This reduces cross-test data collisions.
API Testing and UI Testing Integration
A large Playwright framework should not create all test data through the UI.
Suppose a checkout test requires an existing customer and product.
Creating them through five UI screens makes the test slow.
Instead:
API
↓
Create customer
↓
Create product
↓
UI
↓
Perform checkout
↓
API
↓
Validate order
Example API utility:
import { APIRequestContext } from ‘@playwright/test’;
export class ProductApi {
constructor(
private readonly request: APIRequestContext
) {}
async createProduct(product: {
name: string;
price: number;
}) {
const response = await this.request.post(
‘/api/products’,
{ data: product }
);
if (!response.ok()) {
throw new Error(
`Product creation failed: ${response.status()}`
);
}
return response.json();
}
}
This creates a clean separation:
UI Page Objects → UI behavior
API Clients → backend setup/assertions
Tests → business scenarios
Test Data Management and Environment Configuration
Hard-coded data becomes a major problem at scale.
Avoid:
const email = ‘john@test.com’;
in hundreds of tests.
Instead:
export function createUserData(index = Date.now()) {
return {
name: `Test User ${index}`,
email: `test-${index}@example.com`,
role: ‘customer’
};
}
Then:
const user = createUserData();
For environment configuration:
export const environments = {
local: {
baseURL: ‘http://localhost:3000’
},
staging: {
baseURL: ‘https://staging.example.com’
},
production: {
baseURL: ‘https://example.com’
}
};
Select the environment:
const environment =
process.env.TEST_ENV ?? ‘local’;
const config =
environments[
environment as keyof typeof environments
];
Do not commit passwords, tokens, or production secrets.
Use CI secret management.
Test Isolation and Parallel Execution
Large suites need parallelism.
Playwright runs tests in parallel using worker processes, and workers controls the maximum number of concurrent worker processes.
For example:
workers: process.env.CI ? 4 : undefined
But more workers do not always mean faster tests.
If each worker requires:
- 2 GB RAM
- A browser
- Database connections
- API resources
then 20 workers may overload the CI machine.
A scalable architecture measures:
CPU
Memory
Browser startup time
API capacity
Database capacity
Test duration
Test isolation checklist
Each test should ideally:
- Create its own data
- Use unique identifiers
- Avoid shared mutable state
- Avoid order dependency
- Clean up when necessary
- Work independently
Playwright’s own best-practices guidance recommends parallelism and sharding for scaling test execution.
Worker Configuration and Test Sharding
Workers operate inside a machine.
Shards distribute tests across machines or CI jobs.
CI Pipeline
|
+—+—+—+—+
| | | | |
S1 S2 S3 S4
| | | |
W W W W
Run a shard:
npx playwright test –shard=1/4
Playwright supports sharding with –shard=x/y, allowing test execution across multiple machines.
For example:
2,000 tests
↓
4 shards
↓
~500 tests/shard
↓
Multiple workers/shard
Actual distribution depends on Playwright’s test scheduling, so do not assume every shard contains exactly the same number of tests.
Cross-Browser and Multi-Environment Architecture
Use Playwright projects for different execution configurations.
projects: [
{
name: ‘chromium-staging’,
use: {
…devices[‘Desktop Chrome’],
baseURL: ‘https://staging.example.com’
}
},
{
name: ‘firefox-staging’,
use: {
…devices[‘Desktop Firefox’],
baseURL: ‘https://staging.example.com’
}
},
{
name: ‘webkit-staging’,
use: {
…devices[‘Desktop Safari’],
baseURL: ‘https://staging.example.com’
}
}
]
Projects are designed to run tests against different browsers, devices, configurations, or logical groups.
You can also use projects for:
smoke
regression
mobile
admin
customer
API
visual
Avoid creating hundreds of projects without a clear reason.
Projects multiply execution cost.
Network Mocking and Service Virtualization
Large projects often depend on:
- Payment gateways
- Recommendation engines
- Email services
- Analytics
- Third-party APIs
Not every UI test should call those systems.
Playwright’s routing capabilities allow request interception and response control.
Example:
await page.route(‘**/api/recommendations’, async route => {
await route.fulfill({
status: 200,
contentType: ‘application/json’,
body: JSON.stringify({
products: [
{
id: 1,
name: ‘Recommended Product’
}
]
})
});
});
Use network mocking for deterministic edge cases.
Maintain real integration tests separately.
This keeps the Enterprise Playwright Framework fast without sacrificing integration coverage.
Reporting, Screenshots, Traces, and Test Artifacts
A scalable framework must make failures diagnosable.
Recommended configuration:
use: {
trace: ‘on-first-retry’,
screenshot: ‘only-on-failure’,
video: ‘retain-on-failure’
}
Use:
HTML report → Human investigation
JSON → Automation/analytics
JUnit → CI/test management
Trace → Deep debugging
Screenshot → Visual evidence
Video → Interaction evidence
Keep artifacts in:
test-results/
playwright-report/
For very large suites, avoid storing unnecessary videos for every passing test.
Artifacts consume storage and can increase CI cost.
CI/CD Architecture With GitHub Actions
A scalable pipeline can look like:
Pull Request
|
Type Check
|
Lint
|
Smoke Tests
|
+—-+—-+—-+
| | | |
S1 S2 S3 S4
| | | |
+—-+—-+—-+
|
Merge Reports
|
Publish Artifacts
|
Quality Gate
Example:
name: Playwright Tests
on:
pull_request:
push:
branches:
– main
jobs:
test:
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
runs-on: ubuntu-latest
steps:
– uses: actions/checkout@v6
– uses: actions/setup-node@v6
with:
node-version: lts/*
cache: npm
– run: npm ci
– run: npx tsc –noEmit
– run: npx playwright install –with-deps chromium
– run: >
npx playwright test
–shard=matrix.shardIndex/{{ matrix.shardTotal }}
– uses: actions/upload-artifact@v5
if: !cancelled()with:name:blob-report-{{ matrix.shardIndex }}
path: blob-report/
Playwright’s CI guidance recommends installing browser dependencies in CI and notes sharding as a way to distribute tests across CI jobs.
For sharded reports, use the Blob reporter and merge the shard outputs:
npx playwright merge-reports –reporter html ./all-blob-reports
Playwright’s blob reports retain test results and attachments and are designed to be merged after sharded execution.
Docker-Based Playwright Execution
Docker is useful when environment consistency matters.
Example:
FROM mcr.microsoft.com/playwright:v1.62.0-noble
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npx tsc –noEmit
CMD [“npx”, “playwright”, “test”]
Then:
docker build -t enterprise-playwright .
docker run –rm enterprise-playwright
Containerized execution is particularly useful for keeping browser dependencies and system libraries consistent across CI agents. Playwright provides official Docker images for this purpose.
Pin the image version instead of blindly using a moving tag in a production pipeline.
Real-World Enterprise Playwright Automation Framework Example
Consider an organization with:
Applications:
– Customer Portal
– Admin Portal
– Partner Portal
Tests:
– 1,500 UI tests
– 400 API tests
– 100 visual tests
Browsers:
– Chromium
– Firefox
– WebKit
Environments:
– QA
– Staging
CI:
A suitable architecture:
Test Suites
|
+——————-+——————-+
| | |
Customer Admin Partner
| | |
+——————-+——————-+
|
Fixtures
|
+———–+——-+——-+———–+
| | | |
Auth APIs Test Data Mocks
| | | |
+———–+——-+——-+———–+
|
Playwright Projects
|
+———-+———-+
| | |
Chromium Firefox WebKit
|
Sharding
|
GitHub Actions
|
Blob Aggregation
|
HTML / JUnit / JSON
This structure provides clear ownership.
A customer-portal team can own:
tests/customer/
pages/customer/
while platform automation engineers own:
fixtures/
api/
utils/
config/
This reduces duplicated framework code across teams.
Scaling the Framework Across Multiple Teams
When multiple teams use the same framework, establish ownership boundaries.
Platform team
Owns:
- Playwright configuration
- Fixtures
- CI templates
- Reporting
- Authentication infrastructure
- Shared utilities
Product teams
Own:
- Page objects
- Business tests
- Product-specific data
- Product-specific mocks
QA leadership
Defines:
- Quality gates
- Browser coverage
- CI execution strategy
- Flaky-test policy
- Reporting requirements
A shared framework should not become a dumping ground for every team’s helper method.
Create contribution standards.
For example:
New test
↓
Does it contain reusable behavior?
↓
Yes → fixture/page/component
No → keep it in test
Common Playwright Architecture Mistakes and Solutions
| Mistake | Problem | Better Approach |
| Huge page objects | Difficult maintenance | Split components |
| Hard-coded credentials | Security risk | Environment secrets |
| UI setup for everything | Slow tests | API setup |
| Shared test data | Parallel failures | Unique data |
| Global mutable fixtures | Test interference | Scoped fixtures |
| Too many workers | Resource exhaustion | Measure and tune |
| No sharding | Slow CI | CI matrix/shards |
| Every test full E2E | Slow suite | Layered test strategy |
| Mock everything | Integration gaps | Balanced mocking |
| No traces | Difficult debugging | Retain traces on failure |
| Videos for every test | High storage cost | Failure-only video |
| One giant config | Hard to understand | Config/project separation |
| Duplicate utilities | Maintenance cost | Shared platform layer |
Playwright Architecture Best Practices
Use this checklist when designing a large framework.
Framework structure
- Separate tests from framework infrastructure
- Use Page Objects for reusable UI behavior
- Use components for shared UI sections
- Use fixtures for dependency injection
- Keep API clients separate from UI pages
Test design
- Tests are independent
- Tests use deterministic data
- Tests do not depend on execution order
- Avoid unnecessary UI setup
- Use API setup where appropriate
Execution
- Tune worker count
- Use projects intentionally
- Use sharding for large CI suites
- Keep browser coverage intentional
- Monitor CI resource consumption
Debugging
- Trace failures
- Capture screenshots
- Preserve useful artifacts
- Provide meaningful test names
- Publish reports even after failure
TypeScript quality
Playwright supports TypeScript directly, but Playwright does not perform full TypeScript type-checking during test execution. Run tsc –noEmit separately in CI.
Example:
npx tsc –noEmit
npx playwright test
Also consider ESLint rules that catch missing await calls. Playwright’s best-practices documentation specifically recommends TypeScript and linting for test code.
Playwright Framework Architecture Interview Questions With Answers
1. How would you design Playwright for 1,000+ tests?
Use:
POM
+
Fixtures
+
API clients
+
Test data factories
+
Projects
+
Parallel workers
+
Sharding
+
CI/CD
+
Centralized reporting
The key is separation of responsibility and test isolation.
2. Why use fixtures instead of creating objects inside every test?
Fixtures provide reusable, type-safe dependency injection and centralized setup/teardown.
3. How would you handle authentication?
Use reusable storage state for read-only scenarios. For tests that modify shared server-side state, use separate accounts per worker where necessary.
4. How do workers differ from shards?
Workers parallelize execution within a machine.
Shards distribute the test suite across multiple machines or CI jobs.
5. How would you reduce a two-hour CI suite?
First measure test distribution.
Then consider:
Remove unnecessary waits
↓
Improve test isolation
↓
API-based test setup
↓
Tune workers
↓
Shard across CI jobs
6. Would you use one Page Object for the entire application?
No.
Split pages and reusable components according to application responsibilities.
7. How do you prevent parallel test failures?
Use isolated data, unique identifiers, worker-specific accounts where required, and avoid shared mutable state.
8. How would you handle third-party APIs?
Use network mocking or service virtualization for deterministic UI scenarios while maintaining separate integration coverage against real services.
9. How would you design reporting?
Use built-in HTML/JSON/JUnit/blob reporters where possible and introduce a custom reporter only for organization-specific requirements.
10. What makes an enterprise Playwright architecture maintainable?
Clear ownership, small modules, reusable fixtures, stable APIs, deterministic data, strong typing, consistent conventions, and observable CI execution.
Playwright Architecture Learning Roadmap
If you are transitioning from Selenium to Playwright, use this progression.
Level 1: Fundamentals
Learn:
- TypeScript
- Locators
- Assertions
- Browser contexts
- Playwright Test
Level 2: Framework Development
Learn:
- Page Object Model
- Components
- Fixtures
- Configuration
- Test data
Level 3: Advanced Automation
Learn:
- API testing
- Authentication
- Network mocking
- Visual regression
- Custom fixtures
Level 4: Scalability
Learn:
- Parallel execution
- Workers
- Sharding
- Multi-browser projects
- Test isolation
Level 5: Enterprise Architecture
Learn:
- CI/CD
- Docker
- Reporting
- Custom reporters
- Artifact management
- Framework governance
- Metrics
For deeper learning, explore Advanced Playwright Automation Techniques, Playwright Custom Fixtures Advanced, Playwright Test Sharding Advanced, Playwright Visual Regression Advanced Setup, Playwright Network Mocking Advanced, Playwright Custom Reporter Development, Playwright Page Object Model, Playwright Fixtures Tutorial, Playwright Authentication Tutorial, Playwright API Testing, Playwright Data Driven Testing, Playwright Parallel Execution Tutorial, Playwright Reporting Tutorial, Playwright CI/CD Tutorial, Playwright GitHub Actions Tutorial, Playwright Docker Tutorial, Playwright Framework Design, Playwright TypeScript Tutorial, Playwright Best Practices, and Playwright Interview Questions.
FAQs: Playwright Test Architecture for Large Projects
What is Playwright test architecture for large projects?
It is a layered approach to organizing Playwright tests, fixtures, page objects, APIs, authentication, test data, configuration, reporting, and CI/CD so that hundreds or thousands of tests remain maintainable and reliable.
What is the best folder structure for a large Playwright project?
A practical structure is:
tests/
pages/
components/
fixtures/
api/
auth/
test-data/
utils/
config/
reports/
playwright.config.ts
Each directory should have a clearly defined responsibility.
Should I use Page Object Model in large Playwright projects?
Yes, but avoid giant page classes. Combine Page Objects with reusable components and business-level flows.
How do Playwright fixtures help large projects?
Fixtures centralize setup, teardown, and reusable dependencies. They can provide authenticated pages, API clients, test data, page objects, and other resources.
How do I run thousands of Playwright tests faster?
Use test isolation, API-based setup, parallel workers, and CI sharding. Playwright supports sharding using –shard=x/y.
How many Playwright workers should I use?
There is no universal number. It depends on CPU, memory, browser resource usage, backend capacity, and CI infrastructure. Playwright recommends one worker in CI when stability and reproducibility are more important than maximum parallelism, while stronger infrastructure can support more parallel execution.
What is Playwright project configuration?
A project is a logical test configuration. It can represent browsers, devices, environments, test groups, or different execution settings.
Should Playwright tests create data through the UI?
Not always. API-based setup is usually faster and less brittle for preparing test state.
Should large Playwright frameworks use Docker?
Docker is useful when consistent browser and system dependencies are important, especially in CI/CD.
How should Playwright reports work in a large CI pipeline?
Use a report format suitable for each purpose. For example:
HTML → human investigation
JUnit → CI integration
JSON → automation
Blob → shard aggregation
For sharded execution, Playwright’s blob reporter can be merged into a unified report.
How do I keep a large Playwright framework maintainable?
Use clear module boundaries, reusable fixtures, API utilities, deterministic data, type checking, linting, test isolation, documented conventions, and ownership across teams.
