mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-24 23:15:23 -07:00
tests
This commit is contained in:
parent
e8c64b777b
commit
4114c2356a
5 changed files with 467 additions and 161 deletions
|
@ -4,24 +4,39 @@ import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-prepro
|
||||||
import createEsbuildPlugin from "@badeball/cypress-cucumber-preprocessor/esbuild";
|
import createEsbuildPlugin from "@badeball/cypress-cucumber-preprocessor/esbuild";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
watchForFileChanges: true,
|
// Performance optimizations
|
||||||
video: true,
|
watchForFileChanges: false, // Disable in CI
|
||||||
|
video: false, // Disable video recording for faster runs
|
||||||
|
screenshotOnRunFailure: true,
|
||||||
|
trashAssetsBeforeRuns: true,
|
||||||
|
|
||||||
|
// Security and viewport
|
||||||
chromeWebSecurity: false,
|
chromeWebSecurity: false,
|
||||||
viewportWidth: 2560,
|
viewportWidth: 1920,
|
||||||
viewportHeight: 1440,
|
viewportHeight: 1080,
|
||||||
|
|
||||||
|
// Retry configuration
|
||||||
retries: {
|
retries: {
|
||||||
runMode: 2,
|
runMode: 2,
|
||||||
openMode: 0,
|
openMode: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Environment variables
|
||||||
env: {
|
env: {
|
||||||
username: 'a',
|
username: 'a',
|
||||||
password: 'a',
|
password: 'a',
|
||||||
dockerhost: 'http://172.17.0.1'
|
dockerhost: 'http://172.17.0.1',
|
||||||
|
// Add test environment flags
|
||||||
|
isCI: process.env.CI === 'true',
|
||||||
|
// Add API base URL
|
||||||
|
apiBaseUrl: 'http://localhost:5000/api/v1'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Project configuration
|
||||||
projectId: 'o5451s',
|
projectId: 'o5451s',
|
||||||
|
|
||||||
e2e: {
|
e2e: {
|
||||||
// We've imported your old cypress plugins here.
|
// Setup node events
|
||||||
// You may want to clean this up later by importing these.
|
|
||||||
async setupNodeEvents(
|
async setupNodeEvents(
|
||||||
on: Cypress.PluginEvents,
|
on: Cypress.PluginEvents,
|
||||||
config: Cypress.PluginConfigOptions
|
config: Cypress.PluginConfigOptions
|
||||||
|
@ -35,12 +50,53 @@ export default defineConfig({
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Make sure to return the config object as it might have been modified by the plugin.
|
// Add performance monitoring
|
||||||
|
on('task', {
|
||||||
|
log(message) {
|
||||||
|
console.log(message);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
table(message) {
|
||||||
|
console.table(message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
// return require('./cypress/plugins/index.js')(on, config)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Base configuration
|
||||||
baseUrl: 'http://localhost:5000',
|
baseUrl: 'http://localhost:5000',
|
||||||
specPattern: ['cypress/tests/**/*.spec.ts*', '**/*.feature'],
|
specPattern: [
|
||||||
excludeSpecPattern: ['**/snapshots/*'],
|
'cypress/tests/**/*.spec.ts*',
|
||||||
|
'cypress/features/**/*.feature'
|
||||||
|
],
|
||||||
|
excludeSpecPattern: [
|
||||||
|
'**/snapshots/*',
|
||||||
|
'**/examples/*',
|
||||||
|
'**/*.skip.ts'
|
||||||
|
],
|
||||||
|
|
||||||
|
// Test isolation and performance
|
||||||
|
experimentalRunAllSpecs: true,
|
||||||
|
experimentalModifyObstructiveThirdPartyCode: true,
|
||||||
|
|
||||||
|
// Better error handling
|
||||||
|
defaultCommandTimeout: 10000,
|
||||||
|
requestTimeout: 10000,
|
||||||
|
responseTimeout: 10000,
|
||||||
|
|
||||||
|
// Screenshot and video settings
|
||||||
|
screenshotsFolder: 'cypress/screenshots',
|
||||||
|
videosFolder: 'cypress/videos',
|
||||||
},
|
},
|
||||||
})
|
|
||||||
|
// Component testing (if needed later)
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'angular',
|
||||||
|
bundler: 'webpack',
|
||||||
|
},
|
||||||
|
specPattern: 'cypress/component/**/*.cy.ts',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
@ -1,10 +1,79 @@
|
||||||
|
|
||||||
import { navBar as NavBar } from './shared/NavBar';
|
import { navBar as NavBar } from './shared/NavBar';
|
||||||
export abstract class BasePage {
|
|
||||||
abstract visit(options: Cypress.VisitOptions): Cypress.Chainable<Cypress.AUTWindow>;
|
|
||||||
abstract visit(): Cypress.Chainable<Cypress.AUTWindow>;
|
|
||||||
abstract visit(id: string): Cypress.Chainable<Cypress.AUTWindow>;
|
|
||||||
abstract visit(id: string, options: Cypress.VisitOptions): Cypress.Chainable<Cypress.AUTWindow>;
|
|
||||||
|
|
||||||
|
export abstract class BasePage {
|
||||||
|
// Abstract visit methods
|
||||||
|
abstract visit(options?: any): Cypress.Chainable<any>;
|
||||||
|
abstract visit(id?: string, options?: any): Cypress.Chainable<any>;
|
||||||
|
|
||||||
|
// Common page properties
|
||||||
navbar = NavBar;
|
navbar = NavBar;
|
||||||
|
|
||||||
|
// Common page methods
|
||||||
|
/**
|
||||||
|
* Wait for page to be fully loaded
|
||||||
|
*/
|
||||||
|
waitForPageLoad(): Cypress.Chainable<any> {
|
||||||
|
return cy.get('body').should('be.visible');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if page is loaded by verifying a unique element
|
||||||
|
*/
|
||||||
|
isPageLoaded(uniqueSelector: string): Cypress.Chainable<any> {
|
||||||
|
return cy.get(uniqueSelector).should('exist').then(() => true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll to element with smooth behavior
|
||||||
|
*/
|
||||||
|
scrollToElement(selector: string): Cypress.Chainable<any> {
|
||||||
|
return cy.get(selector).scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for loading spinner to disappear
|
||||||
|
*/
|
||||||
|
waitForLoadingToComplete(): Cypress.Chainable<any> {
|
||||||
|
return cy.get('[data-test="loading-spinner"]', { timeout: 10000 }).should('not.exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get element by data-test attribute
|
||||||
|
*/
|
||||||
|
getByData(selector: string): Cypress.Chainable<any> {
|
||||||
|
return cy.get(`[data-test="${selector}"]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get element by data-test attribute with partial match
|
||||||
|
*/
|
||||||
|
getByDataLike(selector: string): Cypress.Chainable<any> {
|
||||||
|
return cy.get(`[data-test*="${selector}"]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if element is visible and enabled
|
||||||
|
*/
|
||||||
|
isElementInteractive(selector: string): Cypress.Chainable<any> {
|
||||||
|
return cy.get(selector)
|
||||||
|
.should('be.visible')
|
||||||
|
.should('not.be.disabled')
|
||||||
|
.then(() => true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take screenshot of current page
|
||||||
|
*/
|
||||||
|
takeScreenshot(name?: string): Cypress.Chainable<any> {
|
||||||
|
const screenshotName = name || `${this.constructor.name}_${Date.now()}`;
|
||||||
|
return cy.screenshot(screenshotName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log page action for debugging
|
||||||
|
*/
|
||||||
|
logAction(action: string, details?: any): void {
|
||||||
|
cy.log(`[${this.constructor.name}] ${action}`, details);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,147 @@
|
||||||
import { BasePage } from "../base.page";
|
import { BasePage } from "../base.page";
|
||||||
|
|
||||||
class LoginPage extends BasePage {
|
export class LoginPage extends BasePage {
|
||||||
|
// Page selectors
|
||||||
|
private readonly selectors = {
|
||||||
|
username: '#username-field',
|
||||||
|
password: '#password-field',
|
||||||
|
ombiSignInButton: '[data-cy=OmbiButton]',
|
||||||
|
plexSignInButton: '[data-cy=oAuthPlexButton]',
|
||||||
|
loginForm: '[data-test="login-form"]',
|
||||||
|
errorMessage: '[data-test="error-message"]',
|
||||||
|
loadingSpinner: '[data-test="loading-spinner"]'
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// Getters for page elements
|
||||||
get username(): Cypress.Chainable<any> {
|
get username() {
|
||||||
return cy.get('#username-field');
|
return cy.get(this.selectors.username);
|
||||||
}
|
}
|
||||||
|
|
||||||
get password(): Cypress.Chainable<any> {
|
get password() {
|
||||||
return cy.get('#password-field');
|
return cy.get(this.selectors.password);
|
||||||
}
|
}
|
||||||
|
|
||||||
get ombiSignInButton(): Cypress.Chainable<any> {
|
get ombiSignInButton() {
|
||||||
return cy.get('[data-cy=OmbiButton]');
|
return cy.get(this.selectors.ombiSignInButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
get plexSignInButton(): Cypress.Chainable<any> {
|
get plexSignInButton() {
|
||||||
return cy.get('[data-cy=oAuthPlexButton]');
|
return cy.get(this.selectors.plexSignInButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
get loginForm() {
|
||||||
super();
|
return cy.get(this.selectors.loginForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
visit(options: Cypress.VisitOptions): Cypress.Chainable<Cypress.AUTWindow>;
|
get errorMessage() {
|
||||||
visit(): Cypress.Chainable<Cypress.AUTWindow>;
|
return cy.get(this.selectors.errorMessage);
|
||||||
visit(id: string): Cypress.Chainable<Cypress.AUTWindow>;
|
|
||||||
visit(id: string, options: Cypress.VisitOptions): Cypress.Chainable<Cypress.AUTWindow>;
|
|
||||||
visit(id?: any, options?: any) {
|
|
||||||
return cy.visit(`/login`, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get loadingSpinner() {
|
||||||
|
return cy.get(this.selectors.loadingSpinner);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page visit method
|
||||||
|
visit(options?: any): Cypress.Chainable<any> {
|
||||||
|
this.logAction('Visiting login page');
|
||||||
|
return cy.visit('/login', options);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page-specific methods
|
||||||
|
/**
|
||||||
|
* Fill login form with credentials
|
||||||
|
*/
|
||||||
|
fillLoginForm(username: string, password: string): Cypress.Chainable<any> {
|
||||||
|
this.logAction('Filling login form', { username });
|
||||||
|
|
||||||
|
return cy.wrap(null).then(() => {
|
||||||
|
this.username.clear().type(username);
|
||||||
|
this.password.clear().type(password);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit login form
|
||||||
|
*/
|
||||||
|
submitLoginForm(): Cypress.Chainable<any> {
|
||||||
|
this.logAction('Submitting login form');
|
||||||
|
return this.ombiSignInButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete login flow
|
||||||
|
*/
|
||||||
|
login(username: string, password: string): Cypress.Chainable<any> {
|
||||||
|
this.logAction('Performing login', { username });
|
||||||
|
|
||||||
|
return this.fillLoginForm(username, password)
|
||||||
|
.then(() => this.submitLoginForm());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for login to complete
|
||||||
|
*/
|
||||||
|
waitForLoginComplete(): Cypress.Chainable<any> {
|
||||||
|
this.logAction('Waiting for login to complete');
|
||||||
|
|
||||||
|
return cy.url().should('not.include', '/login')
|
||||||
|
.then(() => {
|
||||||
|
this.logAction('Login completed successfully');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if login form is visible
|
||||||
|
*/
|
||||||
|
isLoginFormVisible(): Cypress.Chainable<any> {
|
||||||
|
return this.loginForm.should('be.visible').then(() => true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Plex OAuth button is visible
|
||||||
|
*/
|
||||||
|
isPlexOAuthVisible(): Cypress.Chainable<any> {
|
||||||
|
return this.plexSignInButton.should('be.visible').then(() => true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if error message is displayed
|
||||||
|
*/
|
||||||
|
isErrorMessageVisible(): Cypress.Chainable<any> {
|
||||||
|
return this.errorMessage.should('be.visible').then(() => true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get error message text
|
||||||
|
*/
|
||||||
|
getErrorMessageText(): Cypress.Chainable<any> {
|
||||||
|
return this.errorMessage.invoke('text');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear login form
|
||||||
|
*/
|
||||||
|
clearLoginForm(): Cypress.Chainable<any> {
|
||||||
|
this.logAction('Clearing login form');
|
||||||
|
|
||||||
|
return cy.wrap(null).then(() => {
|
||||||
|
this.username.clear();
|
||||||
|
this.password.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify page is loaded
|
||||||
|
*/
|
||||||
|
verifyPageLoaded(): Cypress.Chainable<any> {
|
||||||
|
this.logAction('Verifying login page is loaded');
|
||||||
|
|
||||||
|
return this.waitForPageLoad()
|
||||||
|
.then(() => this.isLoginFormVisible())
|
||||||
|
.then(() => {
|
||||||
|
this.logAction('Login page loaded successfully');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loginPage = new LoginPage();
|
export const loginPage = new LoginPage();
|
||||||
|
|
|
@ -1,76 +1,93 @@
|
||||||
// ***********************************************
|
// ***********************************************
|
||||||
// This example commands.js shows you how to
|
// Enhanced custom commands with better TypeScript support
|
||||||
// create various custom commands and overwrite
|
|
||||||
// existing commands.
|
|
||||||
//
|
|
||||||
// For more comprehensive examples of custom
|
|
||||||
// commands please read more here:
|
|
||||||
// https://on.cypress.io/custom-commands
|
|
||||||
// ***********************************************
|
// ***********************************************
|
||||||
//
|
|
||||||
//
|
|
||||||
// -- This is a parent command --
|
|
||||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// -- This is a child command --
|
|
||||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// -- This is a dual command --
|
|
||||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// -- This will overwrite an existing command --
|
|
||||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
|
||||||
|
|
||||||
import 'cypress-wait-until';
|
import 'cypress-wait-until';
|
||||||
|
|
||||||
Cypress.Commands.add("landingSettings", (enabled) => {
|
// Type definitions for custom commands
|
||||||
|
declare global {
|
||||||
|
namespace Cypress {
|
||||||
|
interface Chainable {
|
||||||
|
landingSettings(enabled: boolean): Chainable<void>;
|
||||||
|
loginWithCreds(username: string, password: string): Chainable<void>;
|
||||||
|
login(): Chainable<void>;
|
||||||
|
removeLogin(): Chainable<void>;
|
||||||
|
verifyNotification(text: string): Chainable<void>;
|
||||||
|
createUser(username: string, password: string, claims: string[]): Chainable<void>;
|
||||||
|
generateUniqueId(): Chainable<string>;
|
||||||
|
getByData(selector: string): Chainable<JQuery<HTMLElement>>;
|
||||||
|
getByDataLike(selector: string): Chainable<JQuery<HTMLElement>>;
|
||||||
|
triggerHover(elements: JQuery<HTMLElement>): Chainable<void>;
|
||||||
|
waitForApiResponse(alias: string, timeout?: number): Chainable<void>;
|
||||||
|
clearTestData(): Chainable<void>;
|
||||||
|
seedTestData(fixture: string): Chainable<void>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced landing page settings command
|
||||||
|
Cypress.Commands.add("landingSettings", (enabled: boolean) => {
|
||||||
cy.fixture('login/landingPageSettings').then((settings) => {
|
cy.fixture('login/landingPageSettings').then((settings) => {
|
||||||
settings.enabled = enabled;
|
settings.enabled = enabled;
|
||||||
cy.intercept("GET", "**/Settings/LandingPage", settings).as("landingPageSettingsDisabled");
|
cy.intercept("GET", "**/Settings/LandingPage", settings).as("landingPageSettings");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('loginWithCreds', (username, password) => {
|
// Enhanced login with credentials
|
||||||
|
Cypress.Commands.add('loginWithCreds', (username: string, password: string) => {
|
||||||
cy.request({
|
cy.request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: '/api/v1/token',
|
url: '/api/v1/token',
|
||||||
body: {
|
body: { username, password },
|
||||||
username: username,
|
failOnStatusCode: false
|
||||||
password: password,
|
}).then((resp) => {
|
||||||
}
|
if (resp.status === 200) {
|
||||||
})
|
|
||||||
.then((resp) => {
|
|
||||||
window.localStorage.setItem('id_token', resp.body.access_token);
|
window.localStorage.setItem('id_token', resp.body.access_token);
|
||||||
|
cy.log(`Successfully logged in as ${username}`);
|
||||||
|
} else {
|
||||||
|
cy.log(`Login failed for ${username}: ${resp.status}`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('login', () => {
|
// Enhanced default login
|
||||||
|
Cypress.Commands.add('login', () => {
|
||||||
cy.clearLocalStorage();
|
cy.clearLocalStorage();
|
||||||
cy.request({
|
cy.clearCookies();
|
||||||
method: 'POST',
|
|
||||||
url: '/api/v1/token',
|
const username = Cypress.env('username');
|
||||||
body: {
|
const password = Cypress.env('password');
|
||||||
username: Cypress.env('username'),
|
|
||||||
password: Cypress.env('password'),
|
if (!username || !password) {
|
||||||
|
throw new Error('Username and password must be set in environment variables');
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.then((resp) => {
|
|
||||||
window.localStorage.setItem('id_token', resp.body.access_token);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
cy.loginWithCreds(username, password);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enhanced login removal
|
||||||
Cypress.Commands.add('removeLogin', () => {
|
Cypress.Commands.add('removeLogin', () => {
|
||||||
window.localStorage.removeItem('id_token');
|
cy.clearLocalStorage();
|
||||||
|
cy.clearCookies();
|
||||||
|
cy.log('Cleared authentication data');
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('verifyNotification', (text) => {
|
// Enhanced notification verification with better error handling
|
||||||
cy.contains(text, {timeout: 10000});
|
Cypress.Commands.add('verifyNotification', (text: string) => {
|
||||||
|
cy.contains(text, { timeout: 10000 })
|
||||||
|
.should('be.visible')
|
||||||
|
.then(() => {
|
||||||
|
cy.log(`Notification "${text}" verified successfully`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('createUser', (username, password, claims) => {
|
// Enhanced user creation with better error handling
|
||||||
|
Cypress.Commands.add('createUser', (username: string, password: string, claims: string[]) => {
|
||||||
|
const token = window.localStorage.getItem('id_token');
|
||||||
|
if (!token) {
|
||||||
|
throw new Error('No authentication token found. Please login first.');
|
||||||
|
}
|
||||||
|
|
||||||
cy.request({
|
cy.request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: '/api/v1/identity',
|
url: '/api/v1/identity',
|
||||||
|
@ -80,47 +97,78 @@ Cypress.Commands.add('createUser', (username, password, claims) => {
|
||||||
Claims: claims,
|
Claims: claims,
|
||||||
},
|
},
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': 'Bearer ' + window.localStorage.getItem('id_token'),
|
'Authorization': `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
failOnStatusCode: false
|
||||||
|
}).then((resp) => {
|
||||||
|
if (resp.status === 200) {
|
||||||
|
cy.log(`User ${username} created successfully`);
|
||||||
|
} else {
|
||||||
|
cy.log(`Failed to create user ${username}: ${resp.status}`);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
|
// Enhanced unique ID generation
|
||||||
Cypress.Commands.add('generateUniqueId', () => {
|
Cypress.Commands.add('generateUniqueId', () => {
|
||||||
const uniqueSeed = Date.now().toString();
|
const uniqueSeed = Date.now().toString();
|
||||||
const id = Cypress._.uniqueId(uniqueSeed);
|
const id = Cypress._.uniqueId(uniqueSeed);
|
||||||
cy.wrap(id);
|
cy.wrap(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("getByData", (selector, ...args) => {
|
// Enhanced data attribute selectors with better typing
|
||||||
return cy.get(`[data-test=${selector}]`, ...args);
|
Cypress.Commands.add("getByData", (selector: string) => {
|
||||||
|
return cy.get(`[data-test="${selector}"]`);
|
||||||
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add("getByDataLike", (selector: string) => {
|
||||||
|
return cy.get(`[data-test*="${selector}"]`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enhanced hover trigger with better event handling
|
||||||
|
Cypress.Commands.add('triggerHover', function(elements: JQuery<HTMLElement>) {
|
||||||
|
elements.each((index, element) => {
|
||||||
|
const mouseoverEvent = new MouseEvent('mouseover', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
view: window
|
||||||
|
});
|
||||||
|
element.dispatchEvent(mouseoverEvent);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// New command: Wait for API response with timeout
|
||||||
|
Cypress.Commands.add('waitForApiResponse', (alias: string, timeout: number = 10000) => {
|
||||||
|
cy.wait(`@${alias}`, { timeout });
|
||||||
|
});
|
||||||
|
|
||||||
|
// New command: Clear test data
|
||||||
|
Cypress.Commands.add('clearTestData', () => {
|
||||||
|
cy.clearLocalStorage();
|
||||||
|
cy.clearCookies();
|
||||||
|
cy.clearSessionStorage();
|
||||||
|
cy.log('All test data cleared');
|
||||||
|
});
|
||||||
|
|
||||||
|
// New command: Seed test data from fixture
|
||||||
|
Cypress.Commands.add('seedTestData', (fixture: string) => {
|
||||||
|
cy.fixture(fixture).then((data) => {
|
||||||
|
// Implementation depends on your seeding strategy
|
||||||
|
cy.log(`Seeding test data from ${fixture}`);
|
||||||
|
// Example: cy.request('POST', '/api/v1/test/seed', data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Override visit command to add better logging
|
||||||
|
Cypress.Commands.overwrite('visit', (originalFn, url, options) => {
|
||||||
|
cy.log(`Visiting: ${url}`);
|
||||||
|
return originalFn(url, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Override click command to add better logging
|
||||||
|
Cypress.Commands.overwrite('click', (originalFn, subject, options) => {
|
||||||
|
cy.log(`Clicking element: ${subject.selector || 'unknown'}`);
|
||||||
|
return originalFn(subject, options);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Cypress.Commands.add("getByData", (selector) => {
|
|
||||||
return cy.get(`[data-test=${selector}]`);
|
|
||||||
});
|
|
||||||
|
|
||||||
Cypress.Commands.add("getByDataLike", (selector) => {
|
|
||||||
return cy.get(`[data-test*=${selector}]`);
|
|
||||||
});
|
|
||||||
|
|
||||||
Cypress.Commands.add('triggerHover', function(elements) {
|
|
||||||
|
|
||||||
fireEvent(elements, 'mouseover');
|
|
||||||
|
|
||||||
|
|
||||||
function fireEvent(element, event) {
|
|
||||||
if (element.fireEvent) {
|
|
||||||
element.fireEvent('on' + event);
|
|
||||||
} else {
|
|
||||||
const evObj = document.createEvent('Events');
|
|
||||||
|
|
||||||
evObj.initEvent(event, true, false);
|
|
||||||
|
|
||||||
element.dispatchEvent(evObj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,38 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es6",
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2022", "DOM"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"lib": ["es2018", "dom"],
|
"skipLibCheck": true,
|
||||||
"types": ["cypress", "cypress-wait-until", "cypress-image-snapshot", "cypress-real-events", "@bahmutov/cy-api", "node"],
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"types": [
|
||||||
|
"cypress",
|
||||||
|
"cypress-wait-until",
|
||||||
|
"cypress-image-snapshot",
|
||||||
|
"cypress-real-events",
|
||||||
|
"@bahmutov/cy-api",
|
||||||
|
"node"
|
||||||
|
],
|
||||||
"baseUrl": "./cypress",
|
"baseUrl": "./cypress",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*"]
|
"@/*": ["./*"],
|
||||||
|
"@fixtures/*": ["./fixtures/*"],
|
||||||
|
"@page-objects/*": ["./integration/page-objects/*"],
|
||||||
|
"@support/*": ["./support/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"support/*.ts"
|
"support/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
]
|
]
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue