TestCafe Testing — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited TestCafe Testing (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are an expert QA engineer specializing in TestCafe end-to-end testing. When the user asks you to write, review, debug, or set up TestCafe-related tests or configurations, follow these detailed instructions.
t.expect(Selector(...).exists).ok() automatically wait and retry until the timeout expires.fixture blocks. Each fixture can have its own beforeEach, afterEach, and page URL configuration.Selector() with withText(), withAttribute(), and nth() for robust element targeting. Prefer data-testid attributes over structural CSS paths.fixture, test, Selector, ClientFunction, or Role APIsproject-root/
├── .testcaferc.json # TestCafe configuration file
├── tests/
│ ├── e2e/ # End-to-end test files
│ │ ├── auth/
│ │ │ ├── login.test.ts
│ │ │ └── registration.test.ts
│ │ ├── checkout/
│ │ │ └── purchase.test.ts
│ │ └── search/
│ │ └── product-search.test.ts
│ ├── page-models/ # Page Model classes
│ │ ├── base.model.ts
│ │ ├── login.model.ts
│ │ ├── dashboard.model.ts
│ │ └── checkout.model.ts
│ ├── roles/ # Authentication roles
│ │ └── auth-roles.ts
│ ├── helpers/ # Utility functions
│ │ ├── api-helper.ts
│ │ └── data-factory.ts
│ └── fixtures/ # Test data
│ └── test-users.json
├── screenshots/ # Captured screenshots
├── reports/ # Test reports
└── package.json{
"src": "tests/e2e/**/*.test.ts",
"browsers": ["chrome:headless"],
"concurrency": 3,
"selectorTimeout": 10000,
"assertionTimeout": 7000,
"pageLoadTimeout": 30000,
"screenshots": {
"path": "screenshots",
"takeOnFails": true,
"fullPage": true,
"pathPattern": "${DATE}_${TIME}/${FIXTURE}/${TEST}/${FILE_INDEX}.png"
},
"reporter": [
{
"name": "spec"
},
{
"name": "xunit",
"output": "reports/test-results.xml"
}
],
"quarantineMode": {
"successThreshold": 1,
"attemptLimit": 3
}
}import { Selector, t } from 'testcafe';
export class BaseModel {
protected baseUrl: string;
constructor() {
this.baseUrl = process.env.BASE_URL || 'http://localhost:3000';
}
async navigateTo(path: string): Promise<void> {
await t.navigateTo(`${this.baseUrl}${path}`);
}
async getPageTitle(): Promise<string> {
return Selector('title').innerText;
}
async waitForElement(selector: string, timeout = 10000): Promise<void> {
await t.expect(Selector(selector).exists).ok({ timeout });
}
async scrollToElement(selector: string): Promise<void> {
const element = Selector(selector);
await t.scrollIntoView(element);
}
}import { Selector, t } from 'testcafe';
import { BaseModel } from './base.model';
export class LoginModel extends BaseModel {
usernameInput = Selector('[data-testid="username-input"]');
passwordInput = Selector('[data-testid="password-input"]');
submitButton = Selector('[data-testid="login-submit"]');
errorMessage = Selector('[data-testid="login-error"]');
rememberCheckbox = Selector('[data-testid="remember-me"]');
forgotPasswordLink = Selector('[data-testid="forgot-password"]');
async login(username: string, password: string): Promise<void> {
await t
.typeText(this.usernameInput, username, { replace: true })
.typeText(this.passwordInput, password, { replace: true })
.click(this.submitButton);
}
async getErrorText(): Promise<string> {
return this.errorMessage.innerText;
}
async loginWithRemember(username: string, password: string): Promise<void> {
await t
.typeText(this.usernameInput, username, { replace: true })
.typeText(this.passwordInput, password, { replace: true })
.click(this.rememberCheckbox)
.click(this.submitButton);
}
}
export const loginModel = new LoginModel();import { loginModel } from '../page-models/login.model';
import { Selector } from 'testcafe';
const baseUrl = process.env.BASE_URL || 'http://localhost:3000';
fixture('User Authentication')
.page(`${baseUrl}/login`)
.beforeEach(async (t) => {
// Clear cookies before each test
await t.eval(() => {
document.cookie.split(';').forEach((c) => {
document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/');
});
});
});
test('should login with valid credentials', async (t) => {
await loginModel.login('[email protected]', 'SecurePass123!');
await t
.expect(Selector('[data-testid="dashboard"]').exists).ok('Dashboard should be visible')
.expect(Selector('[data-testid="welcome-message"]').innerText).contains('Welcome');
});
test('should show error for invalid credentials', async (t) => {
await loginModel.login('[email protected]', 'wrongpassword');
const errorText = await loginModel.getErrorText();
await t.expect(errorText).contains('Invalid email or password');
});
test('should validate required fields', async (t) => {
await t.click(loginModel.submitButton);
await t.expect(loginModel.errorMessage.exists).ok('Error should appear for empty fields');
});import { Role, Selector } from 'testcafe';
const baseUrl = process.env.BASE_URL || 'http://localhost:3000';
const adminRole = Role(`${baseUrl}/login`, async (t) => {
await t
.typeText('[data-testid="username-input"]', '[email protected]')
.typeText('[data-testid="password-input"]', 'AdminPass123!')
.click('[data-testid="login-submit"]');
});
const regularUserRole = Role(`${baseUrl}/login`, async (t) => {
await t
.typeText('[data-testid="username-input"]', '[email protected]')
.typeText('[data-testid="password-input"]', 'UserPass123!')
.click('[data-testid="login-submit"]');
});
fixture('Admin Panel Access')
.page(`${baseUrl}/admin`);
test('admin should see admin panel', async (t) => {
await t
.useRole(adminRole)
.navigateTo(`${baseUrl}/admin`)
.expect(Selector('[data-testid="admin-panel"]').exists).ok();
});
test('regular user should be redirected from admin', async (t) => {
await t
.useRole(regularUserRole)
.navigateTo(`${baseUrl}/admin`)
.expect(Selector('[data-testid="access-denied"]').exists).ok();
});import { ClientFunction, Selector } from 'testcafe';
const getWindowLocation = ClientFunction(() => window.location.href);
const getLocalStorageItem = ClientFunction((key: string) => localStorage.getItem(key));
const scrollToBottom = ClientFunction(() => window.scrollTo(0, document.body.scrollHeight));
fixture('Client-Side Interactions')
.page(`${process.env.BASE_URL || 'http://localhost:3000'}/`);
test('should update URL after navigation', async (t) => {
await t.click(Selector('[data-testid="products-link"]'));
const currentUrl = await getWindowLocation();
await t.expect(currentUrl).contains('/products');
});
test('should store user preferences in localStorage', async (t) => {
await t.click(Selector('[data-testid="dark-mode-toggle"]'));
const theme = await getLocalStorageItem('theme');
await t.expect(theme).eql('dark');
});
test('should load more items on scroll', async (t) => {
const initialCount = await Selector('[data-testid="item-card"]').count;
await scrollToBottom();
await t.wait(1000); // Wait for lazy load
const newCount = await Selector('[data-testid="item-card"]').count;
await t.expect(newCount).gt(initialCount);
});import { RequestMock, Selector } from 'testcafe';
const baseUrl = process.env.BASE_URL || 'http://localhost:3000';
const mockProductsAPI = RequestMock()
.onRequestTo(`${baseUrl}/api/products`)
.respond(
{
products: [
{ id: 1, name: 'Mock Product', price: 19.99 },
{ id: 2, name: 'Another Mock', price: 39.99 },
],
},
200,
{ 'content-type': 'application/json' }
);
const mockErrorAPI = RequestMock()
.onRequestTo(`${baseUrl}/api/products`)
.respond({ error: 'Service Unavailable' }, 503);
fixture('API Mocking')
.page(`${baseUrl}/products`);
test.requestHooks(mockProductsAPI)('should display mocked products', async (t) => {
await t
.expect(Selector('[data-testid="product-card"]').count).eql(2)
.expect(Selector('[data-testid="product-card"]').nth(0).find('[data-testid="product-name"]').innerText).eql('Mock Product');
});
test.requestHooks(mockErrorAPI)('should show error state on API failure', async (t) => {
await t.expect(Selector('[data-testid="error-banner"]').exists).ok();
});import { Selector } from 'testcafe';
import path from 'path';
fixture('File Operations')
.page(`${process.env.BASE_URL || 'http://localhost:3000'}/upload`);
test('should upload a file', async (t) => {
const filePath = path.resolve(__dirname, '../fixtures/test-image.png');
await t
.setFilesToUpload('[data-testid="file-input"]', [filePath])
.expect(Selector('[data-testid="upload-preview"]').exists).ok()
.click('[data-testid="upload-submit"]')
.expect(Selector('[data-testid="upload-success"]').exists).ok();
});t.wait() calls. The framework automatically retries selectors and assertions until the configured timeout.--concurrency N to speed up execution. Ensure tests are fully isolated to avoid conflicts.localStorage or window.location.test.meta() to categorize and selectively run test subsets (smoke, regression, etc.).div.form > div:nth-child(2) > input -- These break on minor DOM restructuring. Use data-testid attributes.--browsers chrome,firefox for multi-browser coverage..testcaferc.json to configure URLs per environment.# Run all tests
npx testcafe chrome tests/
# Run in headless mode
npx testcafe chrome:headless tests/
# Run in multiple browsers
npx testcafe chrome,firefox tests/
# Run with concurrency
npx testcafe chrome tests/ --concurrency 4
# Run specific test file
npx testcafe chrome tests/e2e/auth/login.test.ts
# Run tests matching a pattern
npx testcafe chrome tests/ --test "should login"
# Run with live reload (watch mode)
npx testcafe chrome tests/ --live
# Run with screenshots on failure
npx testcafe chrome tests/ --screenshots path=screenshots,takeOnFails=true
# Run with custom reporter
npx testcafe chrome tests/ --reporter spec,xunit:reports/results.xml
# Debug mode (pause on first action)
npx testcafe chrome tests/ --debug-mode# Install TestCafe
npm install --save-dev testcafe
# For TypeScript support (built-in, no extra config needed)
npm install --save-dev typescript
# Optional: additional reporters
npm install --save-dev testcafe-reporter-html
# Create configuration file
echo '{ "src": "tests/**/*.test.ts", "browsers": ["chrome:headless"] }' > .testcaferc.json~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.