Best for
- When maintaining an existing Protractor test suite for an Angular application
- When debugging failing Protractor tests
- When migrating Protractor tests to Playwright, Cypress, or another modern framework
PramodDutta/qaskills/seed-skills/protractor-testing/SKILL.md
Comprehensive Protractor end-to-end testing skill for Angular and AngularJS applications with Angular-specific locators, automatic waitForAngular synchronization, Page Object patterns, and migration guidance to modern frameworks.
Decision brief
You are an expert QA engineer specializing in Protractor end-to-end testing for Angular applications. When the user asks you to write, review, debug, or set up Protractor-related tests, or to migrate from Protractor to a modern framework, follow these detailed instructions.
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Declared | Source record | Install path and trigger |
| Claude Code | Declared | Source record | Install path and trigger |
| Cursor | Declared | Source record | Install path and trigger |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/PramodDutta/qaskills --skill "seed-skills/protractor-testing"Inspect the Agent Skill "Protractor Testing" from https://github.com/PramodDutta/qaskills/blob/fb3fbec70591bad971dd97c5d9add6eaa99bae18/seed-skills/protractor-testing/SKILL.md at commit fb3fbec70591bad971dd97c5d9add6eaa99bae18. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
Review the “ExpectedConditions Usage” section in the pinned source before continuing.
Review the “Setup” section in the pinned source before continuing.
1. Angular-Aware Testing -- Protractor's key strength is automatic synchronization with Angular's change detection via waitForAngular(). Understand when this helps and when you need to disable it for non-Angular pages. 2. Angular-Specific Locators -- Use by.model(), by.binding()…
When maintaining an existing Protractor test suite for an Angular application
Review the “Project Structure” section in the pinned source before continuing.
Permission review
The documentation includes network, browsing, or remote request actions.
baseUrl: process.env.BASE_URL || 'http://localhost:4200',The documentation includes network, browsing, or remote request actions.
await browser.get('https://external-service.example.com/login');The documentation asks the agent to run terminal commands or scripts.
npx protractor protractor.conf.jsThe documentation asks the agent to run terminal commands or scripts.
npx protractor protractor.conf.js --specs=e2e/specs/auth/login.spec.tsEvidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 91/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 211 | Source | Repository attention, not individual Skill quality |
| Compatibility | 3 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
You are an expert QA engineer specializing in Protractor end-to-end testing for Angular applications. When the user asks you to write, review, debug, or set up Protractor-related tests, or to migrate from Protractor to a modern framework, follow these detailed instructions.
Important Note: Protractor reached end-of-life in 2023 and is no longer actively maintained. For new projects, recommend Playwright or Cypress. This skill covers maintaining existing Protractor test suites and migrating them to modern alternatives.
waitForAngular(). Understand when this helps and when you need to disable it for non-Angular pages.by.model(), by.binding(), by.repeater(), and by.cssContainingText() for Angular/AngularJS-specific element targeting when they are available.browser.wait() with ExpectedConditions for explicit waits on specific elements or states.beforeEach rather than relying on other tests to create state.async/await in all new code.protractor.conf.js, element(by.model()), browser.get(), or browser.wait()project-root/
├── protractor.conf.js # Protractor configuration
├── e2e/
│ ├── specs/ # Test spec files
│ │ ├── auth/
│ │ │ ├── login.spec.ts
│ │ │ └── registration.spec.ts
│ │ ├── dashboard/
│ │ │ └── widgets.spec.ts
│ │ └── forms/
│ │ └── contact-form.spec.ts
│ ├── page-objects/ # Page Object classes
│ │ ├── base.po.ts
│ │ ├── login.po.ts
│ │ ├── dashboard.po.ts
│ │ └── form.po.ts
│ ├── helpers/ # Utility functions
│ │ ├── wait-helpers.ts
│ │ └── api-helpers.ts
│ └── fixtures/ # Test data
│ └── test-users.json
├── reports/ # Test reports
├── screenshots/ # Failure screenshots
└── package.json
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 30000,
specs: ['./e2e/specs/**/*.spec.ts'],
capabilities: {
browserName: 'chrome',
chromeOptions: {
args: process.env.CI
? ['--headless', '--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage']
: [],
},
},
directConnect: true,
baseUrl: process.env.BASE_URL || 'http://localhost:4200',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 60000,
print: function () {},
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json'),
});
jasmine.getEnv().addReporter(
new SpecReporter({
spec: { displayStacktrace: 'pretty' },
})
);
// Screenshot on failure
const originalAddExpectationResult = jasmine.Spec.prototype.addExpectationResult;
jasmine.Spec.prototype.addExpectationResult = function () {
if (!arguments[0]) {
browser.takeScreenshot().then((png) => {
const fs = require('fs');
const stream = fs.createWriteStream(`screenshots/failure-${Date.now()}.png`);
stream.write(Buffer.from(png, 'base64'));
stream.end();
});
}
return originalAddExpectationResult.apply(this, arguments);
};
},
};
import { browser, element, by, ExpectedConditions as EC } from 'protractor';
export class BasePage {
async navigateTo(path: string): Promise<void> {
await browser.get(path);
}
async waitForElement(locator: any, timeout = 10000): Promise<void> {
await browser.wait(EC.visibilityOf(element(locator)), timeout);
}
async waitForElementToDisappear(locator: any, timeout = 10000): Promise<void> {
await browser.wait(EC.invisibilityOf(element(locator)), timeout);
}
async getText(locator: any): Promise<string> {
await this.waitForElement(locator);
return element(locator).getText();
}
async click(locator: any): Promise<void> {
await browser.wait(EC.elementToBeClickable(element(locator)), 10000);
await element(locator).click();
}
async type(locator: any, text: string): Promise<void> {
await this.waitForElement(locator);
const el = element(locator);
await el.clear();
await el.sendKeys(text);
}
async getCurrentUrl(): Promise<string> {
return browser.getCurrentUrl();
}
async getTitle(): Promise<string> {
return browser.getTitle();
}
}
import { by } from 'protractor';
import { BasePage } from './base.po';
export class LoginPage extends BasePage {
private locators = {
usernameInput: by.css('[data-testid="username-input"]'),
passwordInput: by.css('[data-testid="password-input"]'),
submitButton: by.css('[data-testid="login-submit"]'),
errorMessage: by.css('[data-testid="login-error"]'),
forgotPasswordLink: by.css('[data-testid="forgot-password"]'),
// Angular-specific locators for AngularJS apps
emailModel: by.model('user.email'),
passwordModel: by.model('user.password'),
};
async login(username: string, password: string): Promise<void> {
await this.type(this.locators.usernameInput, username);
await this.type(this.locators.passwordInput, password);
await this.click(this.locators.submitButton);
}
async getError(): Promise<string> {
return this.getText(this.locators.errorMessage);
}
async open(): Promise<void> {
await this.navigateTo('/login');
}
}
import { browser, ExpectedConditions as EC, element, by } from 'protractor';
import { LoginPage } from '../page-objects/login.po';
describe('User Authentication', () => {
const loginPage = new LoginPage();
beforeEach(async () => {
await loginPage.open();
});
it('should login with valid credentials', async () => {
await loginPage.login('[email protected]', 'SecurePass123!');
const url = await browser.getCurrentUrl();
expect(url).toContain('/dashboard');
});
it('should show error for invalid credentials', async () => {
await loginPage.login('[email protected]', 'wrongpassword');
const error = await loginPage.getError();
expect(error).toContain('Invalid email or password');
});
it('should redirect to requested page after login', async () => {
await browser.get('/profile');
// Should redirect to login
const loginUrl = await browser.getCurrentUrl();
expect(loginUrl).toContain('/login');
await loginPage.login('[email protected]', 'SecurePass123!');
const profileUrl = await browser.getCurrentUrl();
expect(profileUrl).toContain('/profile');
});
});
import { element, by } from 'protractor';
describe('Angular Form (AngularJS)', () => {
it('should bind input to model', async () => {
await browser.get('/contact');
// AngularJS-specific locators
const nameInput = element(by.model('contact.name'));
await nameInput.sendKeys('John Doe');
// Check binding
const displayedName = element(by.binding('contact.name'));
expect(await displayedName.getText()).toBe('John Doe');
});
it('should iterate over repeater elements', async () => {
await browser.get('/contacts');
const contacts = element.all(by.repeater('contact in contacts'));
const count = await contacts.count();
expect(count).toBeGreaterThan(0);
// Access specific item in repeater
const firstName = contacts.get(0).element(by.binding('contact.name'));
expect(await firstName.getText()).toBeTruthy();
});
it('should use cssContainingText for text-based selection', async () => {
await browser.get('/nav');
const settingsLink = element(by.cssContainingText('.nav-item', 'Settings'));
await settingsLink.click();
expect(await browser.getCurrentUrl()).toContain('/settings');
});
});
describe('Non-Angular Page Interactions', () => {
it('should handle non-Angular login page', async () => {
// Disable Angular synchronization for non-Angular pages
await browser.waitForAngularEnabled(false);
await browser.get('https://external-service.example.com/login');
await element(by.css('#username')).sendKeys('admin');
await element(by.css('#password')).sendKeys('password123');
await element(by.css('#login-btn')).click();
// Wait manually since Angular sync is off
await browser.wait(
EC.urlContains('/dashboard'),
10000,
'Expected redirect to dashboard'
);
// Re-enable for Angular pages
await browser.waitForAngularEnabled(true);
});
});
import { browser, element, by, ExpectedConditions as EC } from 'protractor';
describe('Advanced Wait Patterns', () => {
it('should wait for element visibility', async () => {
await browser.get('/dashboard');
const widget = element(by.css('[data-testid="analytics-widget"]'));
await browser.wait(EC.visibilityOf(widget), 15000, 'Widget did not appear');
expect(await widget.isDisplayed()).toBe(true);
});
it('should wait for text in element', async () => {
await browser.get('/status');
const statusEl = element(by.css('[data-testid="status-text"]'));
await browser.wait(EC.textToBePresentInElement(statusEl, 'Connected'), 10000);
expect(await statusEl.getText()).toContain('Connected');
});
it('should combine conditions with AND/OR', async () => {
const modal = element(by.css('[data-testid="modal"]'));
const overlay = element(by.css('[data-testid="overlay"]'));
// Wait for BOTH modal and overlay to be visible
await browser.wait(
EC.and(EC.visibilityOf(modal), EC.visibilityOf(overlay)),
10000,
'Modal or overlay did not appear'
);
});
});
// PROTRACTOR (old)
import { browser, element, by, ExpectedConditions as EC } from 'protractor';
await browser.get('/login');
await element(by.css('[data-testid="username"]')).sendKeys('[email protected]');
await element(by.css('[data-testid="password"]')).sendKeys('pass123');
await element(by.css('[data-testid="submit"]')).click();
await browser.wait(EC.urlContains('/dashboard'), 10000);
// PLAYWRIGHT (new)
import { test, expect } from '@playwright/test';
await page.goto('/login');
await page.locator('[data-testid="username"]').fill('[email protected]');
await page.locator('[data-testid="password"]').fill('pass123');
await page.locator('[data-testid="submit"]').click();
await expect(page).toHaveURL(/.*dashboard/);
// PROTRACTOR page object
export class LoginPageProtractor {
usernameInput = element(by.css('[data-testid="username"]'));
async login(user: string, pass: string) {
await this.usernameInput.sendKeys(user);
await element(by.css('[data-testid="password"]')).sendKeys(pass);
await element(by.css('[data-testid="submit"]')).click();
}
}
// PLAYWRIGHT page object
export class LoginPagePlaywright {
constructor(private page: Page) {}
async login(user: string, pass: string) {
await this.page.locator('[data-testid="username"]').fill(user);
await this.page.locator('[data-testid="password"]').fill(pass);
await this.page.locator('[data-testid="submit"]').click();
}
}
async/await for clarity and future migration compatibility.data-testid selectors over Angular-specific locators (by.model, by.binding) for new tests. These translate directly to modern frameworks during migration.ExpectedConditions for explicit waits rather than browser.sleep(). Combine conditions with EC.and() and EC.or() for complex wait scenarios.chromeOptions.args with --headless. This reduces resource usage and speeds up pipeline execution.browser.waitForAngularEnabled(false). Forgetting this causes infinite waits on non-Angular content.allScriptsTimeout for page loads, defaultTimeoutInterval for individual tests, and specific timeouts for browser.wait() calls.directConnect: true to bypass Selenium Server and connect directly to ChromeDriver. This is faster and simpler for single-browser local testing.browser.sleep() -- Static waits slow tests and hide timing issues. Use browser.wait() with ExpectedConditions for reliable synchronization.async/await consistently.by.xpath() for simple queries -- XPath is slower and harder to read than CSS selectors. Only use XPath when CSS cannot express the query.shardTestFiles: true with maxInstances for parallelism.# Run all tests
npx protractor protractor.conf.js
# Run specific spec
npx protractor protractor.conf.js --specs=e2e/specs/auth/login.spec.ts
# Run with specific base URL
npx protractor protractor.conf.js --baseUrl=http://staging.example.com
# Update WebDriver binaries
npx webdriver-manager update
# Start Selenium Server (if not using directConnect)
npx webdriver-manager start
# Run with verbose logging
npx protractor protractor.conf.js --troubleshoot
# Install Protractor
npm install --save-dev protractor
# Install TypeScript support
npm install --save-dev typescript ts-node @types/jasmine @types/jasminewd2
# Install reporter
npm install --save-dev jasmine-spec-reporter
# Update browser drivers
npx webdriver-manager update
Frequently asked questions
You are an expert QA engineer specializing in Protractor end-to-end testing for Angular applications. When the user asks you to write, review, debug, or set up Protractor-related tests, or to migrate from Protractor to a modern framework, follow these detailed instructions.
The source record exposes this install command: npx skills add https://github.com/PramodDutta/qaskills --skill "seed-skills/protractor-testing". Inspect the command and pinned source before running it.
The pinned source record declares support for: codex, claude code, cursor.
Static rules flagged network, exec-script in the source; the page lists the matching lines and excerpts.
Alternatives
PramodDutta/qaskills
Optimize resumes for Applicant Tracking Systems, check ATS compatibility, and analyze keyword match
PramodDutta/qaskills
Generate optimized test combinations using pairwise (all-pairs) testing algorithms to achieve maximum coverage with minimum test cases across multiple input parameters
PramodDutta/qaskills
Gate RAG pipelines in CI with versioned golden eval sets, per-metric thresholds, baseline drift detection, and a build that fails when retrieval or answer quality regresses.
PramodDutta/qaskills
Generate comprehensive test cases from state machine models covering all states, transitions, guard conditions, and invalid transition attempts for workflow-heavy features