Protractor Testing — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Protractor 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 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.jsonconst { 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.by.model, by.binding) for new tests. These translate directly to modern frameworks during migration.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.browser.wait() with ExpectedConditions for reliable synchronization.async/await consistently.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~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.