'use strict';

var test$1 = require('@playwright/test');
var winston = require('winston');
var axios = require('axios');
var AxeBuilder = require('@axe-core/playwright');

const logger = winston.createLogger({
    level: process.env.CI ? 'silent' : 'info',
    format: winston.format.combine(winston.format.colorize(), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.printf((info) => {
        const { timestamp, level, message, ...meta } = info;
        const formattedMeta = Object.keys(meta).length
            ? `\n${JSON.stringify(meta, undefined, 2)}`
            : '';
        return `${timestamp} ${level}: ${message} ${formattedMeta}`;
    })),
    transports: [new winston.transports.Console()],
});

/**
 * Check if the current browser is Firefox
 */
function isFirefox(page) {
    var _a;
    return ((_a = page.context().browser()) === null || _a === void 0 ? void 0 : _a.browserType().name()) === 'firefox';
}
/**
 * Check if an error is a navigation aborted error (Firefox-specific)
 */
function isNavigationAbortedError(error) {
    var _a;
    const message = (_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : '';
    return message.includes('NS_BINDING_ABORTED') || message.includes('Navigation interrupted');
}
/**
 * Check if an error is a Safari navigation policy error
 */
function isSafariNavigationPolicyError(error) {
    var _a;
    const message = (_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : '';
    return message.includes('Navigation canceled by policy check');
}
/**
 * Check if the current URL matches a given pattern
 */
function urlMatchesPattern(currentUrl, pattern) {
    try {
        const url = new URL(currentUrl);
        if (typeof pattern === 'function')
            return pattern(url);
        if (pattern instanceof RegExp)
            return pattern.test(currentUrl);
        // Glob pattern - convert to regex
        const regexPattern = pattern
            .replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape special regex chars except * and ?
            .replace(/\*\*/g, '.*')
            .replace(/\*/g, '[^/]*');
        return new RegExp(`^${regexPattern}$`).test(currentUrl);
    }
    catch (_a) {
        return false;
    }
}
/**
 * Safe wrapper around page.waitForURL that handles browser-specific navigation errors
 *
 * Firefox has stricter navigation lifecycle handling that can cause NS_BINDING_ABORTED
 * errors when multiple rapid navigations occur (React router redirects, auth redirects).
 *
 * Safari can throw "Navigation canceled by policy check" errors during cross-origin
 * navigations or when security policies interrupt the navigation flow.
 *
 * This utility:
 * 1. Uses `waitUntil: 'commit'` for Firefox (faster, avoids abort)
 * 2. Includes retry logic for NS_BINDING_ABORTED errors (Firefox)
 * 3. Handles "Navigation canceled by policy check" errors (Safari)
 * 4. Checks if URL is already correct before failing
 */
async function safeWaitForURL(page, urlPattern, options = {}) {
    const { timeout = 60000, waitUntil = isFirefox(page) ? 'commit' : 'domcontentloaded', maxRetries = 3, } = options;
    let lastError = null;
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            await page.waitForLoadState('load', { timeout });
            await page.waitForURL(urlPattern, { timeout, waitUntil });
            return;
        }
        catch (error) {
            lastError = error;
            // Handle Firefox NS_BINDING_ABORTED errors
            if (isNavigationAbortedError(error)) {
                // Check if we're already at the target URL
                if (urlMatchesPattern(page.url(), urlPattern)) {
                    return;
                }
                if (attempt < maxRetries) {
                    // Exponential backoff between retries
                    await page.waitForTimeout(500 * attempt);
                    continue;
                }
            }
            // Handle Safari navigation policy errors
            if (isSafariNavigationPolicyError(error)) {
                await page.waitForTimeout(3000);
                await page.waitForLoadState('domcontentloaded');
                // Check if we're at the target URL after waiting
                if (urlMatchesPattern(page.url(), urlPattern)) {
                    return;
                }
                if (attempt < maxRetries) {
                    continue;
                }
            }
            throw error;
        }
    }
    throw lastError;
}

async function waitForPageLoad(page) {
    logger.info(`Waiting for ${page.url()} load...`);
    try {
        logger.info('waiting for page load');
        await page.waitForLoadState('load', { timeout: 60000 });
        logger.info('waiting for dom content to be loaded');
        await page.waitForLoadState('domcontentloaded', { timeout: 60000 });
        logger.info('waiting for network idle');
        await page.waitForLoadState('networkidle', { timeout: 60000 });
        await page
            .waitForResponse((response) => response.status() === 200, {
            timeout: 10000,
        })
            .catch(() => {
            logger.info('No more network requests detected');
        });
        logger.info('checking for dialog');
        const dialog = await Promise.race([
            page.waitForEvent('dialog', { timeout: 1000 }).catch(() => null),
            Promise.resolve(null),
        ]);
        if (dialog) {
            logger.info('dialog detected');
            logger.info(`Dismissing dialog with message: ${dialog.message()}`);
            await dialog.dismiss();
        }
    }
    catch (error) {
        logger.error('Error waiting for page load:', error);
        throw error;
    }
}
async function closeWithEsc(page) {
    await page.waitForTimeout(500);
    await page.keyboard.press('Escape');
    await page.waitForTimeout(500);
}
const test = test$1.test.extend({
    step: async ({ page }, use, testInfo) => {
        await use(async (title, fn) => {
            await fn();
        });
    },
});
const retry = async (action, errorMessage, retryCount = 5) => {
    let count = 0;
    while (count < retryCount) {
        try {
            await action();
            break;
        }
        catch (error) {
            count += 1;
            logger.error(`${errorMessage}: ${error}`);
        }
    }
};
const isJSON = (value) => {
    try {
        JSON.parse(value);
        return true;
    }
    catch (error) {
        return false;
    }
};

class MailsacClient {
    constructor(apiKey) {
        this.baseUrl = 'https://mailsac.com/api';
        this.apiKey = apiKey;
    }
    /**
     * Get all emails for a specific inbox
     * @param inbox - The inbox address (e.g., 'test@mailsac.com' or just 'test')
     */
    async getEmails(inbox) {
        var _a;
        const formattedInbox = inbox.includes('@') ? inbox : `${inbox}@mailsac.com`;
        try {
            const response = await axios.get(`${this.baseUrl}/addresses/${formattedInbox}/messages`, {
                headers: {
                    'Mailsac-Key': this.apiKey,
                },
            });
            return response.data;
        }
        catch (error) {
            if (axios.isAxiosError(error)) {
                console.error('Error fetching emails:', ((_a = error.response) === null || _a === void 0 ? void 0 : _a.data) || error.message);
            }
            else {
                console.error('Error fetching emails:', error);
            }
            throw error;
        }
    }
    /**
     * Get a specific email by ID
     * @param email - The email address
     * @param messageId - The ID of the email to fetch
     */
    async getEmailById(email, messageId) {
        var _a;
        const formattedEmail = email.includes('@') ? email : `${email}@mailsac.com`;
        try {
            const response = await axios.get(`${this.baseUrl}/raw/${formattedEmail}/${messageId}`, {
                headers: {
                    'Mailsac-Key': this.apiKey,
                },
            });
            return response.data;
        }
        catch (error) {
            if (axios.isAxiosError(error)) {
                console.error('Error fetching email content:', ((_a = error.response) === null || _a === void 0 ? void 0 : _a.data) || error.message);
            }
            else {
                console.error('Error fetching email content:', error);
            }
            throw error;
        }
    }
    /**
     * Delete a specific email by ID
     * @param emailId - The ID of the email to delete
     */
    async deleteEmail(emailId) {
        var _a;
        try {
            const response = await axios.delete(`${this.baseUrl}/messages/${emailId}`, {
                headers: {
                    'Mailsac-Key': this.apiKey,
                },
            });
            return response.data;
        }
        catch (error) {
            if (axios.isAxiosError(error)) {
                console.error('Error deleting email:', ((_a = error.response) === null || _a === void 0 ? void 0 : _a.data) || error.message);
            }
            else {
                console.error('Error deleting email:', error);
            }
            throw error;
        }
    }
}

async function signUpWithEmailAndPassword(page, authHost, postSignUpUrl, credentials, alreadyInSignupPage = false) {
    logger.info('Checking for user credentials');
    if (!credentials) {
        const timeStamp = Date.now();
        logger.info(`Generating credentials for test ${timeStamp}`);
        credentials = {
            email: `test+${timeStamp}@ibleducation.com`,
            password: 'test-password',
        };
    }
    if (!alreadyInSignupPage) {
        // Click on the sign up button
        const signUpButton = page.getByRole('button', { name: 'Sign Up' });
        await test$1.expect(signUpButton).toBeVisible({ timeout: 10000 });
        await signUpButton.click();
        // Wait for the sign up page to load
        await safeWaitForURL(page, (url) => url.href.startsWith(authHost + '/account/create'), {
            timeout: 60000,
        });
    }
    // Click on the continue with Password button
    const continueWithPasswordButton = page.getByRole('button', {
        name: 'continue with Password',
    });
    await test$1.expect(continueWithPasswordButton).toBeVisible({ timeout: 10000 });
    await continueWithPasswordButton.click();
    // Fill in Email field
    const emailInputField = page.getByPlaceholder('Email');
    await test$1.expect(emailInputField).toBeVisible();
    await emailInputField.fill(credentials.email);
    // Fill in Password field
    const passwordInputField = page.getByPlaceholder('Password', {
        exact: true,
    });
    await test$1.expect(passwordInputField).toBeVisible();
    await passwordInputField.fill(credentials.password);
    // Fill in Confirm Password field
    const confirmPasswordInputField = page.getByPlaceholder('Confirm Password');
    await test$1.expect(confirmPasswordInputField).toBeVisible();
    await confirmPasswordInputField.fill(credentials.password);
    // Click Create Account
    const createAccountButton = page.getByRole('button', {
        name: 'Create Account',
    });
    await test$1.expect(createAccountButton).toBeVisible();
    await createAccountButton.click();
    // Wait to be redirected back to application
    await safeWaitForURL(page, (url) => url.href.startsWith(postSignUpUrl), {
        timeout: 60000,
    });
    await page.waitForLoadState('domcontentloaded');
    await page.waitForTimeout(15000);
}
async function loginWithEmailAndPassword(page, username, password, hostUrl, postLoginUrlMatcher) {
    //NEW AUTH FLOW
    await page.click('button:has-text("Continue with Password")');
    logger.info('Waiting for 1 second before waiting for the email input');
    await page.waitForTimeout(1000);
    logger.info('Waiting for the password form to appear');
    await page.waitForSelector('input[type="email"]');
    logger.info('Filling in the login credentials');
    await page.fill('input[type="email"]', username);
    await page.fill('input[type="password"]', password);
    logger.info('Clicking the login button');
    await page.click('button:has-text("Continue")');
    await page.waitForTimeout(500);
    if (postLoginUrlMatcher) {
        await safeWaitForURL(page, (url) => postLoginUrlMatcher(url), {
            timeout: 80000,
        });
    }
}
async function loginWithMicrosoftIdp(page, username, password) {
    // Wait for SSO provider page to load
    await safeWaitForURL(page, /.*\/oauth2\/v2.0\/authorize.*/, {
        timeout: 60000,
    });
    await page.waitForLoadState('networkidle');
    const inputEmailEl = page.getByRole('textbox', { name: 'NetId@syr.edu' });
    await inputEmailEl.waitFor({ state: 'visible' });
    inputEmailEl.fill(username);
    const nextBtnEl = page.getByRole('button', { name: 'Next' });
    await nextBtnEl.waitFor({ state: 'visible' });
    nextBtnEl.click();
    const inputPasswordEl = page.getByPlaceholder('Password');
    await inputPasswordEl.waitFor({ state: 'visible' });
    inputPasswordEl.fill(password);
    const signinBtnEl = page.getByRole('button', { name: 'Sign in' });
    await signinBtnEl.waitFor({ state: 'visible' });
    signinBtnEl.click();
    const confirmStaySignIn = page.getByRole('button', { name: 'Yes' });
    await confirmStaySignIn.waitFor({ state: 'visible' });
    confirmStaySignIn.click();
}
async function canChatWithEmbedMentor(page, openChatButton) {
    await openChatButton.click();
    await page.waitForTimeout(20000);
    const widget = page.locator('#ibl-chat-widget-container');
    await test$1.expect(widget).toBeVisible();
    const mentorIframe = widget.frameLocator('iframe').nth(0);
    const navName = mentorIframe.locator('nav h1');
    await test$1.expect(navName).toBeVisible({ timeout: 15000 });
    const closeButton = mentorIframe.getByRole('button', {
        name: /close chat/i,
    });
    await test$1.expect(closeButton).toBeVisible();
    const img = mentorIframe.locator('img[data-slot="avatar-image"]').first();
    await test$1.expect(img).toBeVisible();
    await test$1.expect(img).toHaveAttribute('src');
    const text = 'Whats the context of this page';
    const textArea = mentorIframe.locator('textarea[placeholder]:not([placeholder=""])');
    await test$1.expect(textArea).toBeVisible();
    await test$1.expect(textArea).toHaveAttribute('placeholder', /.+/);
    const sendButton = mentorIframe.getByRole('button', {
        name: /send message/i,
    });
    await textArea.fill(text);
    await test$1.expect(sendButton).toBeEnabled();
    await sendButton.click();
    await page.waitForTimeout(5000);
    const userMessage = mentorIframe.locator('.chat-user-message-query', {
        hasText: text,
    });
    const mentorResponse = mentorIframe.locator('.chat-ai-message-response');
    await test$1.expect(userMessage).toBeVisible({ timeout: 10000 });
    await mentorResponse.waitFor({ state: 'visible' });
    await test$1.expect(mentorResponse).toBeVisible();
    await test$1.expect(mentorResponse).toContainText(/courses/i);
    // Check user message layout
    const userLayout = await userMessage.evaluate((el) => {
        const elem = el;
        return {
            scrollWidth: elem.scrollWidth,
            clientWidth: elem.clientWidth,
            height: elem.offsetHeight,
            overflows: elem.scrollWidth > elem.clientWidth,
        };
    });
    test$1.expect(userLayout.overflows).toBe(false);
    test$1.expect(userLayout.height).toBeGreaterThan(10);
    // Check mentor message layout
    const mentorLayout = await mentorResponse.evaluate((el) => {
        const elem = el;
        return {
            scrollWidth: elem.scrollWidth,
            clientWidth: elem.clientWidth,
            height: elem.offsetHeight,
            overflows: elem.scrollWidth > elem.clientWidth,
        };
    });
    test$1.expect(mentorLayout.overflows).toBe(false);
    test$1.expect(mentorLayout.height).toBeGreaterThan(10);
    await closeButton.click();
    await test$1.expect(widget).not.toBeVisible();
}
function getMentorIdFromUrl(url) {
    const { pathname } = new URL(url);
    const match = pathname.startsWith('/platform/');
    if (!match) {
        return null;
    }
    const parts = pathname.split('/').filter(Boolean); // ['platform', '<tenantKey>', '<mentorId>']
    return parts[2];
}

class AuthFlowBuilder {
    constructor(flow = 'username_password', idp = '', authHost = '') {
        this.flow = flow;
        this.idp = idp;
        this.authHost = authHost;
    }
    async run(page, step, username, password, hostUrl, authUrl, postLoginUrlMatcher) {
        switch (this.flow) {
            case 'username_password':
                return this.usernamePasswordFlow(page, username, password, hostUrl, authUrl, postLoginUrlMatcher);
            case 'sso':
                return this.ssoFlow(page, username, password, hostUrl, authUrl);
            case 'direct_sso':
                return this.directSsoFlow(page, step, username, password, hostUrl, authUrl);
            default:
                throw new Error(`Unknown auth flow: ${this.flow}`);
        }
    }
    // Username/password flow
    async usernamePasswordFlow(page, username, password, hostUrl, authUrl, postLoginUrlMatcher) {
        logger.info('Running username/password auth flow');
        return loginWithEmailAndPassword(page, username, password, hostUrl, postLoginUrlMatcher);
    }
    async magicLinkFlow(page) {
        logger.info('Magic link required for authentication');
        logger.info('Fill the email address field');
        await page.getByPlaceholder('Email').click();
        await page.getByPlaceholder('Email').fill('ibltestuser@mailsac.com');
        logger.info('Click the Continue button');
        await page.click('button:has-text("Continue")');
        await page.getByText(/Enter it below/i).waitFor({ timeout: 5000 });
        logger.info('Check for the magic link email');
        await page.waitForTimeout(5000);
        const apiKey = 'k_GZgb33koIUEPRRqciDzn6c21bZnNBit0GBQaV0a905hwwc832';
        const mailsac = new MailsacClient(apiKey);
        const emailInbox = 'ibltestuser';
        const emails = await mailsac.getEmails(emailInbox);
        const magicLinkEmail = emails.find((e) => e.subject === 'Verify your email to sign in for ibl');
        if (!magicLinkEmail) {
            throw new Error('Magic link email not found');
        }
        const emailContent = await mailsac.getEmailById(emailInbox, magicLinkEmail._id);
        const matches = emailContent.match(/\b\d{6}\b/);
        const signInCode = matches ? matches[0] : '';
        await page.keyboard.press(signInCode[0]);
        await page.keyboard.press(signInCode[1]);
        await page.keyboard.press(signInCode[2]);
        await page.keyboard.press(signInCode[3]);
        await page.keyboard.press(signInCode[4]);
        await page.keyboard.press(signInCode[5]);
    }
    // SSO flow (using platform's SSO IDP)
    async ssoFlow(page, username, password, hostUrl, authUrl) {
        logger.info(`Running SSO auth flow with IDP: ${this.idp}`);
        await page.goto(authUrl);
        await page.waitForLoadState('domcontentloaded');
        await page.click(`button:has-text("Continue with ${this.idp}")`);
        await page.waitForURL(/\/sso-login-complete\?data=.*/);
        await page.waitForLoadState('networkidle');
        logger.info('SSO flow completed');
    }
    // Direct SSO flow (using client-specific SSO IDP)
    async directSsoFlow(page, step, username, password, hostUrl, authUrl) {
        logger.info(`Running direct SSO auth flow with client IDP: ${this.idp}`);
        await page.goto(authUrl);
        await page.waitForLoadState('domcontentloaded');
        loginWithMicrosoftIdp(page, username, password);
        logger.info('Direct SSO flow completed');
    }
}

/**
 * Creates a Playwright setup test function for authentication.
 * Use this as the test body in your auth.setup.ts file:
 *
 * ```ts
 * import { test as setup } from '@iblai/iblai-js/playwright';
 * import { createAuthSetup } from '@iblai/iblai-js/playwright';
 *
 * setup('authenticate', createAuthSetup({
 *   hostUrl: process.env.MENTOR_NEXTJS_HOST || '',
 *   authHost: process.env.AUTH_HOST || '',
 *   appName: 'mentor',
 *   postLoginUrlMatcher: (url) => url.href.includes('/platform/'),
 * }));
 * ```
 */
function createAuthSetup(config) {
    return async ({ browser, step }, testInfo) => {
        const { hostUrl, authHost, appName, postLoginUrlMatcher, authFlow = process.env.AUTH_FLOW || 'username_password', authIdp = process.env.AUTH_IDP || '', } = config;
        // Get browser key from project name (e.g., 'setup-chrome' -> 'CHROME')
        const browserKey = testInfo.project.name.replace('setup-', '').toUpperCase();
        // Use browser-specific credentials with fallback to default
        const username = process.env[`PLAYWRIGHT_USERNAME_${browserKey}`] || process.env.PLAYWRIGHT_USERNAME || '';
        const password = process.env[`PLAYWRIGHT_PASSWORD_${browserKey}`] || process.env.PLAYWRIGHT_PASSWORD || '';
        // Use browser-specific auth file
        const authFile = `playwright/.auth/user-${browserKey.toLowerCase()}.json`;
        logger.info(`Authenticating for browser: ${browserKey} with user: ${username}`);
        const context = await browser.newContext({
            userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36',
        });
        test.setTimeout(200000); // 200 seconds
        const page = await context.newPage();
        await page.goto(hostUrl, { timeout: 60000, waitUntil: 'domcontentloaded' });
        await safeWaitForURL(page, (url) => url.href.includes(`/login?app=${appName}&redirect-to=${hostUrl}`), { timeout: 60000 });
        try {
            await page.waitForLoadState('domcontentloaded', { timeout: 60000 });
            await test$1.expect(page.getByRole('button', { name: 'Continue with Password' })).toBeVisible({
                timeout: 60000,
            });
        }
        catch (error) {
            logger.info(`############# Auth page load timeout: ${error.message}`);
        }
        logger.info('Authentication page loaded successfully');
        // Use AuthFlowBuilder for all flows
        const authFlowBuilder = new AuthFlowBuilder(authFlow, authIdp, authHost);
        await authFlowBuilder.run(page, step, username, password, hostUrl, authHost, postLoginUrlMatcher);
        try {
            logger.info('Waiting for url ', hostUrl);
            await safeWaitForURL(page, (url) => postLoginUrlMatcher(url), { timeout: 60000 });
            logger.info('After waiting for url ', hostUrl);
        }
        catch (e) {
            logger.info('Timeout waiting for URL: ', e.message + ' Continuing with tests');
        }
        const dmToken = await page.evaluate(() => localStorage.getItem('dm_token'));
        const axdToken = await page.evaluate(() => localStorage.getItem('axd_token'));
        test$1.expect(dmToken).not.toBeNull();
        test$1.expect(axdToken).not.toBeNull();
        await context.storageState({ path: authFile });
        logger.info('Authentication test completed successfully.');
        // Close page first, wait for stability, then close context with error handling
        // This prevents Chromium crashes (SIGSEGV) that can occur when closing context abruptly
        await page.waitForTimeout(500);
        await page.close();
        try {
            await context.close();
        }
        catch (e) {
            logger.info('Context close failed (may be Chromium crash), continuing...', e.message);
        }
    };
}

class CustomReporter {
    onBegin(config, suite) {
        if (suite.allTests().length === 0) {
            logger.info('No tests found');
            process.exit(0); // Exit gracefully when no tests are found
        }
        logger.info('Test run started');
    }
    onTestBegin(test) {
        logger.info(`Starting test: ${test.title}`);
    }
    onTestEnd(test, result) {
        var _a;
        logger.info(`Test ${test.title} finished with status: ${result.status}`);
        logger.info(`Test logs for ${test.title}:`);
        if (result.status === 'failed') {
            logger.info(`Test ${test.title} failed with error: ${(_a = result.error) === null || _a === void 0 ? void 0 : _a.message}`);
        }
    }
    onTimeout(timeout) {
        console.warn(`Timeout occurred: ${timeout}`);
    }
    onEnd(result) {
        logger.info(`Test run finished with status: ${result.status}`);
        logger.info('Overall logs:');
    }
    onStdOut(chunk, test, result) {
        logger.info(`[STDOUT] ${chunk.toString()}`);
    }
    onStdErr(chunk, test, result) {
        logger.error(`[STDERR] ${chunk.toString()}`);
    }
}

/**
 * Wait for a page to be fully ready (DOM complete + brief delay for lazy rendering)
 */
async function waitForPageReady(page, timeout = 30000) {
    await page.waitForFunction(() => document.readyState === 'complete', {
        timeout,
    });
    await page.waitForTimeout(2000);
}
/**
 * Check if the current user is an admin by reading localStorage
 */
async function checkAdminStatus(page) {
    try {
        await page.waitForFunction(() => {
            return window.localStorage.getItem('current_tenant') !== null;
        }, { timeout: 10000 });
        const isAdmin = await page.evaluate(() => {
            const raw = window.localStorage.getItem('current_tenant');
            if (!raw)
                return false;
            try {
                const parsed = JSON.parse(raw);
                return parsed.is_admin === true;
            }
            catch (_a) {
                return false;
            }
        });
        console.log('Admin status check result:', isAdmin);
        return isAdmin;
    }
    catch (error) {
        console.log('Error checking admin status:', error.message);
        return false;
    }
}
/**
 * Wait for an element to be fully stable (attached, visible, not animating)
 */
async function waitForElementStable(page, locator, timeout = 10000) {
    await locator.waitFor({ state: 'attached', timeout });
    await test$1.expect(locator).toBeVisible({ timeout });
    await page.waitForTimeout(500);
    return locator;
}
/**
 * Click an element with retry logic and stability checks
 */
async function reliableClick(page, locator, timeout = 10000, maxRetries = 3) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            await waitForElementStable(page, locator, timeout);
            await locator.scrollIntoViewIfNeeded();
            await page.waitForTimeout(200);
            await locator.click({ force: true });
            break;
        }
        catch (error) {
            console.log(`Click attempt ${attempt} failed:`, error.message);
            if (attempt === maxRetries) {
                throw new Error(`Failed to click element after ${maxRetries} attempts: ${error.message}`);
            }
            await page.waitForTimeout(1000);
        }
    }
}
/**
 * Fill an input element with retry logic, clearing first and verifying the value
 */
async function reliableFill(page, locator, value, timeout = 10000, maxRetries = 3) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            await waitForElementStable(page, locator, timeout);
            await locator.clear();
            await locator.fill(value);
            const actualValue = await locator.inputValue();
            if (actualValue !== value) {
                throw new Error(`Value mismatch: expected "${value}", got "${actualValue}"`);
            }
            break;
        }
        catch (error) {
            console.log(`Fill attempt ${attempt} failed:`, error.message);
            if (attempt === maxRetries) {
                throw new Error(`Failed to fill element after ${maxRetries} attempts: ${error.message}`);
            }
            await page.waitForTimeout(1000);
        }
    }
}
/**
 * Wait for a dialog element to be visible and its content loaded
 */
async function waitForDialogReady(page, dialogLocator, timeout = 15000) {
    await test$1.expect(dialogLocator).toBeVisible({ timeout });
    await page.waitForLoadState('domcontentloaded', { timeout });
    await page.waitForTimeout(1000);
    return dialogLocator;
}
/**
 * Select a date from a calendar component, navigating months if needed
 */
async function selectDateFromCalendar(page, dialogLocator, targetDate, timeout = 15000) {
    const dd = String(targetDate.getDate()).padStart(2, '0');
    const mm = String(targetDate.getMonth() + 1).padStart(2, '0');
    const yyyy = targetDate.getFullYear();
    const formattedDate = `${dd}/${mm}/${yyyy}`;
    console.log(`Attempting to select date: ${formattedDate}`);
    const calendar = dialogLocator.locator('[data-slot="calendar"]');
    await calendar.waitFor({ state: 'visible', timeout });
    await page.waitForTimeout(1000);
    const dateFormats = [
        formattedDate, // DD/MM/YYYY
        `${mm}/${dd}/${yyyy}`, // MM/DD/YYYY
        `${yyyy}-${mm}-${dd}`, // YYYY-MM-DD
    ];
    let dayButton = null;
    for (const format of dateFormats) {
        dayButton = dialogLocator.locator(`button[data-day="${format}"]:not([disabled])`);
        if (await dayButton.isVisible()) {
            console.log(`Found date with format: ${format}`);
            break;
        }
    }
    // If date not found, try navigating months
    if (!dayButton || !(await dayButton.isVisible())) {
        console.log('Date not in current month, attempting navigation...');
        let attempts = 0;
        const maxAttempts = 3;
        while (attempts < maxAttempts) {
            const nextMonthButton = dialogLocator.getByRole('button', {
                name: /next month/i,
            });
            if (await nextMonthButton.isVisible()) {
                await reliableClick(page, nextMonthButton);
                await page.waitForTimeout(1000);
                for (const format of dateFormats) {
                    dayButton = dialogLocator.locator(`button[data-day="${format}"]:not([disabled])`);
                    if (await dayButton.isVisible()) {
                        console.log(`Found date after navigation with format: ${format}`);
                        break;
                    }
                }
                if (dayButton && (await dayButton.isVisible())) {
                    break;
                }
            }
            else {
                console.log('Next month button not available');
                break;
            }
            attempts++;
        }
    }
    // Final verification and selection
    if (!dayButton || !(await dayButton.isVisible())) {
        const allDates = await dialogLocator.locator('button[data-day]').all();
        console.log('Available dates:');
        for (const date of allDates) {
            const dateAttr = await date.getAttribute('data-day');
            const disabled = await date.getAttribute('disabled');
            const visible = await date.isVisible();
            console.log({ date: dateAttr, disabled, visible });
        }
        throw new Error(`Could not find selectable date button for ${formattedDate}`);
    }
    await reliableClick(page, dayButton);
    console.log(`Successfully selected date: ${formattedDate}`);
}
/**
 * Run axe-core accessibility checks against a page
 */
async function expectNoAccessibilityViolations(page) {
    const results = await new AxeBuilder({ page })
        .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
        .options({
        rules: {
            'color-contrast': { enabled: false },
        },
    })
        .analyze();
    test$1.expect(results.violations).toEqual([]);
}
/**
 * Run axe-core accessibility checks against visible dialogs
 */
async function expectNoAccessibilityViolationsOnDialogs(page, rules, exclude = ['#embed-mentor-preview']) {
    const dialogLocator = page.locator('[role="dialog"]');
    try {
        await test$1.expect(dialogLocator.first()).toBeVisible({ timeout: 10000 });
    }
    catch (_a) {
        const pageUrl = page.url();
        const dialogCount = await dialogLocator.count();
        console.log(`[expectNoAccessibilityViolationsOnDialogs] Debug info:
      - Page URL: ${pageUrl}
      - Dialog count: ${dialogCount}`);
        throw new Error('expectNoAccessibilityViolationsOnDialogs: No visible element with [role="dialog"] found on page. ' +
            `Page URL: ${pageUrl}. Ensure the dialog is visible and has role="dialog" attribute.`);
    }
    const results = await new AxeBuilder({ page })
        .include('[role="dialog"]')
        .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
        .options({
        rules: {
            'color-contrast': { enabled: false },
            ...rules,
        },
    })
        .exclude(exclude)
        .analyze();
    test$1.expect(results.violations).toEqual([]);
}

async function inviteUserTest(page, inviteModal) {
    const emailInput = inviteModal.locator('#email-invite');
    await test$1.expect(emailInput).toBeVisible({ timeout: 10000 });
    const uniqueEmail = `test+user+${Date.now()}@test.com`;
    await emailInput.fill(uniqueEmail);
    const submitBtn = inviteModal.getByRole('button', { name: 'Send Invite' });
    await test$1.expect(submitBtn).toBeEnabled({ timeout: 10000 });
    await submitBtn.click();
    await test$1.expect(submitBtn).toBeDisabled({ timeout: 15000 });
    await page.waitForLoadState('networkidle');
}
async function navigateToAccountComponent(page, profileBtn) {
    await profileBtn.waitFor({ state: 'visible' });
    await profileBtn.click();
    const profileMenuItem = page.getByRole('menuitem', { name: 'Profile' });
    await test$1.expect(profileMenuItem).toBeVisible();
    const tenantBtn = page.getByRole('menuitem').nth(1);
    await test$1.expect(tenantBtn).toBeVisible();
    await tenantBtn.click();
    const tenantDialog = page.getByRole('dialog').filter({ hasText: 'organization' });
    await test$1.expect(tenantDialog).toBeVisible();
    return tenantDialog;
}
async function navigateToDataReports(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await test$1.expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    await safeWaitForURL(page, /\/analytics\/reports$/);
    logger.info('Successfully navigated to Data Reports page');
}
async function shouldDisplayReportCards(page, REPORT_CARDS) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await test$1.expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const emptyState = page.getByText('No reports available yet.');
    const isEmptyState = await emptyState.isVisible().catch(() => false);
    if (isEmptyState) {
        await test$1.expect(emptyState).toBeVisible();
        logger.info('No reports available - empty state displayed');
        return;
    }
    for (const report of REPORT_CARDS) {
        const reportCard = page.getByLabel(report.ariaLabel);
        const isVisible = await reportCard.isVisible().catch(() => false);
        if (isVisible) {
            await test$1.expect(reportCard).toBeVisible();
            const heading = reportCard.getByRole('heading', {
                name: report.name,
                level: 3,
            });
            await test$1.expect(heading).toBeVisible();
            const downloadButton = reportCard.getByRole('button', {
                name: 'Download report',
            });
            await test$1.expect(downloadButton).toBeVisible();
            await test$1.expect(downloadButton).toBeEnabled();
            logger.info(`Report card "${report.name}" displayed correctly`);
        }
    }
}
async function shouldOpenCSVEditorDialog(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await test$1.expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const userReportCard = page.getByLabel('User Report report card');
    const isVisible = await userReportCard.isVisible().catch(() => false);
    if (!isVisible) {
        logger.info('User Report card not visible - skipping test');
        test$1.skip();
        return;
    }
    const downloadButton = userReportCard.getByRole('button', {
        name: 'Download report',
    });
    await downloadButton.click();
    const csvEditorDialog = page.getByRole('dialog', {
        name: 'Edit CSV Data',
    });
    await test$1.expect(csvEditorDialog).toBeVisible({ timeout: 60000 });
    const dialogTitle = csvEditorDialog.getByRole('heading', {
        name: 'Edit CSV Data',
        level: 2,
    });
    await test$1.expect(dialogTitle).toBeVisible();
    const csvTable = csvEditorDialog.getByRole('table', {
        name: 'CSV data table',
    });
    await test$1.expect(csvTable).toBeVisible();
    await test$1.expect(csvEditorDialog.getByRole('button', { name: 'Cancel' })).toBeVisible();
    await test$1.expect(csvEditorDialog.getByRole('button', { name: 'Save' })).toBeVisible();
    await test$1.expect(csvEditorDialog.getByRole('button', { name: 'Close' }).first()).toBeVisible();
    logger.info('CSV Editor dialog opened successfully');
}
async function shouldDisplayCSVInEditableTableFormat(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await test$1.expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const userReportCard = page.getByLabel('User Report report card');
    const isVisible = await userReportCard.isVisible().catch(() => false);
    if (!isVisible) {
        test$1.skip();
        return;
    }
    const downloadButton = userReportCard.getByRole('button', {
        name: 'Download report',
    });
    await downloadButton.click();
    const csvEditorDialog = page.getByRole('dialog', {
        name: 'Edit CSV Data',
    });
    await test$1.expect(csvEditorDialog).toBeVisible({ timeout: 60000 });
    const table = csvEditorDialog.getByRole('table', {
        name: 'CSV data table',
    });
    await test$1.expect(table).toBeVisible();
    const headerCells = table.locator('thead th');
    const headerCount = await headerCells.count();
    test$1.expect(headerCount).toBeGreaterThan(0);
    const dataRows = table.locator('tbody tr');
    const rowCount = await dataRows.count();
    test$1.expect(rowCount).toBeGreaterThan(0);
    logger.info(`CSV table has ${headerCount} columns and ${rowCount - 1} data rows (excluding Add Row)`);
}
async function shouldAllowEditingCellValuesInCSVEditor(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await test$1.expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const userReportCard = page.getByLabel('User Report report card');
    const isVisible = await userReportCard.isVisible().catch(() => false);
    if (!isVisible) {
        test$1.skip();
        return;
    }
    const downloadButton = userReportCard.getByRole('button', {
        name: 'Download report',
    });
    await downloadButton.click();
    const csvEditorDialog = page.getByRole('dialog', {
        name: 'Edit CSV Data',
    });
    await test$1.expect(csvEditorDialog).toBeVisible({ timeout: 60000 });
    const table = csvEditorDialog.getByRole('table', {
        name: 'CSV data table',
    });
    const firstEditableCell = table.locator('tbody tr').first().locator('td').nth(1);
    const isCellVisible = await firstEditableCell.isVisible().catch(() => false);
    if (!isCellVisible) {
        logger.info('No editable cells found - skipping edit test');
        test$1.skip();
        return;
    }
    const initialButton = firstEditableCell.locator('button, div[role="button"]').first();
    const initialText = await initialButton.textContent();
    await initialButton.click();
    const cellInput = firstEditableCell.locator('input');
    await test$1.expect(cellInput).toBeVisible({ timeout: 5000 });
    const testValue = 'Edited Test Value';
    await cellInput.fill(testValue);
    await cellInput.press('Enter');
    await test$1.expect(firstEditableCell).toContainText(testValue);
    logger.info(`Successfully edited cell value from "${initialText}" to "${testValue}"`);
}
async function shouldAddNewRowWhenClickingAddRowButton(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await test$1.expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const userReportCard = page.getByLabel('User Report report card');
    const isVisible = await userReportCard.isVisible().catch(() => false);
    if (!isVisible) {
        test$1.skip();
        return;
    }
    const downloadButton = userReportCard.getByRole('button', {
        name: 'Download report',
    });
    await downloadButton.click();
    const csvEditorDialog = page.getByRole('dialog', {
        name: 'Edit CSV Data',
    });
    await test$1.expect(csvEditorDialog).toBeVisible({ timeout: 60000 });
    const table = csvEditorDialog.locator('table');
    const initialRowCount = await table.locator('tbody tr').count();
    const addRowButton = csvEditorDialog.getByRole('button', {
        name: 'Add Row',
    });
    await test$1.expect(addRowButton).toBeVisible();
    await addRowButton.click();
    const newRowCount = await table.locator('tbody tr').count();
    test$1.expect(newRowCount).toBe(initialRowCount + 1);
    logger.info(`Added new row. Row count changed from ${initialRowCount} to ${newRowCount}`);
}
async function shouldSaveEditedCSVAndTriggerDownload(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await test$1.expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const userReportCard = page.getByLabel('User Report report card');
    const isVisible = await userReportCard.isVisible().catch(() => false);
    if (!isVisible) {
        test$1.skip();
        return;
    }
    const downloadButton = userReportCard.getByRole('button', {
        name: 'Download report',
    });
    await downloadButton.click();
    await page.waitForLoadState('networkidle');
    const csvEditorDialog = page.getByRole('dialog', {
        name: 'Edit CSV Data',
    });
    await test$1.expect(csvEditorDialog).toBeVisible({ timeout: 60000 });
    const downloadPromise = page.waitForEvent('download', { timeout: 30000 });
    const saveButton = csvEditorDialog.getByRole('button', { name: 'Save' });
    await saveButton.click();
    const download = await downloadPromise;
    const filename = download.suggestedFilename();
    test$1.expect(filename).toBe('report.csv');
    await test$1.expect(csvEditorDialog).not.toBeVisible({ timeout: 5000 });
    logger.info(`CSV file "${filename}" downloaded successfully`);
}
async function shouldCloseCSVEditorWithoutSavingWhenClickingCancel(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await test$1.expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const userReportCard = page.getByLabel('User Report report card');
    const isVisible = await userReportCard.isVisible().catch(() => false);
    if (!isVisible) {
        test$1.skip();
        return;
    }
    const downloadButton = userReportCard.getByRole('button', {
        name: 'Download report',
    });
    await downloadButton.click();
    await page.waitForLoadState('networkidle');
    const csvEditorDialog = page.getByRole('dialog', {
        name: 'Edit CSV Data',
    });
    await test$1.expect(csvEditorDialog).toBeVisible({ timeout: 60000 });
    const cancelButton = csvEditorDialog.getByRole('button', {
        name: 'Cancel',
    });
    await cancelButton.click();
    await test$1.expect(csvEditorDialog).not.toBeVisible({ timeout: 5000 });
    logger.info('CSV Editor dialog closed via Cancel button');
}
async function shouldCloseCSVEditorWhenClickingCloseButton(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await test$1.expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const userReportCard = page.getByLabel('User Report report card');
    await test$1.expect(userReportCard).toBeVisible({ timeout: 120000 });
    const isVisible = await userReportCard.isVisible().catch(() => false);
    if (!isVisible) {
        test$1.skip();
        return;
    }
    const downloadButton = userReportCard.getByRole('button', {
        name: 'Download report',
    });
    await downloadButton.click();
    await page.waitForLoadState('networkidle');
    const csvEditorDialog = page.getByRole('dialog', {
        name: 'Edit CSV Data',
    });
    await test$1.expect(csvEditorDialog).toBeVisible({ timeout: 60000 });
    const closeButton = csvEditorDialog
        .getByRole('button', {
        name: 'Close',
    })
        .first();
    await closeButton.click();
    await test$1.expect(csvEditorDialog).not.toBeVisible({ timeout: 5000 });
    logger.info('CSV Editor dialog closed via Close button');
}
async function shouldVerifyCSVEditorDialogAccessibility(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await test$1.expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const userReportCard = page.getByLabel('User Report report card');
    const isVisible = await userReportCard.isVisible().catch(() => false);
    if (!isVisible) {
        test$1.skip();
        return;
    }
    const downloadButton = userReportCard.getByRole('button', {
        name: 'Download report',
    });
    await downloadButton.click();
    await page.waitForLoadState('networkidle');
    const csvEditorDialog = page.getByRole('dialog', {
        name: 'Edit CSV Data',
    });
    await test$1.expect(csvEditorDialog).toBeVisible({ timeout: 60000 });
    await test$1.expect(csvEditorDialog).toHaveAttribute('role', 'dialog');
    const csvTable = csvEditorDialog.getByRole('table', {
        name: 'CSV data table',
    });
    await test$1.expect(csvTable).toBeVisible();
    const saveButton = csvEditorDialog.getByRole('button', {
        name: 'Save changes and download CSV',
    });
    await test$1.expect(saveButton).toBeVisible();
    const cancelButton = csvEditorDialog.getByRole('button', {
        name: 'Cancel editing and close dialog',
    });
    await test$1.expect(cancelButton).toBeVisible();
    const addRowButton = csvEditorDialog.getByRole('button', {
        name: 'Add Row',
    });
    await test$1.expect(addRowButton).toBeVisible();
    const editorRegion = csvEditorDialog.getByRole('region', {
        name: 'CSV data editor',
    });
    await test$1.expect(editorRegion).toBeVisible();
    logger.info('CSV Editor dialog has proper ARIA labels and roles');
}
async function shouldOpenCSVEditorForUserMetadataReport(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await test$1.expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const userMetadataReportCard = page.getByLabel('User Metadata Report report card');
    const isVisible = await userMetadataReportCard.isVisible().catch(() => false);
    if (!isVisible) {
        test$1.skip();
        return;
    }
    const downloadButton = userMetadataReportCard.getByRole('button', {
        name: 'Download report',
    });
    await downloadButton.click();
    await page.waitForLoadState('networkidle');
    const csvEditorDialog = page.getByRole('dialog', {
        name: 'Edit CSV Data',
    });
    await test$1.expect(csvEditorDialog).toBeVisible({ timeout: 60000 });
    const table = csvEditorDialog.locator('table');
    const companyHeader = table.locator('thead th input[value="company"]');
    const hasCompanyColumn = await companyHeader.isVisible().catch(() => false);
    if (hasCompanyColumn) {
        logger.info('User Metadata Report contains company column as expected');
    }
    await csvEditorDialog.getByRole('button', { name: 'Close' }).first().click();
    await test$1.expect(csvEditorDialog).not.toBeVisible({ timeout: 5000 });
}
async function shouldDirectlyDownloadChatHistoryReportWithoutCSVEditor(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await test$1.expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const chatHistoryCard = page.getByLabel('Chat History');
    if (!(await chatHistoryCard.isVisible().catch(() => false))) {
        test$1.skip();
        return;
    }
    const downloadButton = chatHistoryCard.getByRole('button', {
        name: 'Download report',
    });
    const downloadPromise = page.waitForEvent('download', { timeout: 120000 });
    await test$1.expect(downloadButton).toBeVisible({ timeout: 30000 });
    await downloadButton.click();
    await page.waitForLoadState('networkidle');
    const spinner = downloadButton.locator('[class*="animate-spin"]');
    await test$1.expect(spinner).toBeVisible({ timeout: 30000 });
    try {
        const download = await downloadPromise;
        const filename = download.suggestedFilename();
        test$1.expect(filename.endsWith('.csv')).toBeTruthy();
        logger.info(`Chat History report downloaded: ${filename}`);
    }
    catch (_a) {
        logger.info('Chat History download may have completed without triggering download event');
    }
    const csvEditorDialog = page.getByRole('dialog', {
        name: 'Edit CSV Data',
    });
    const dialogOpened = await csvEditorDialog.isVisible().catch(() => false);
    test$1.expect(dialogOpened).toBeFalsy();
}
async function shouldDisableOtherDownloadButtonsWhileGeneratingReport(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await test$1.expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const downloadButtons = page.getByRole('button', {
        name: 'Download report',
    });
    const buttonCount = await downloadButtons.count();
    if (buttonCount < 2) {
        logger.info('Not enough report cards to test disabled state - skipping');
        test$1.skip();
        return;
    }
    await downloadButtons.first().click();
    await page.waitForTimeout(500);
    const secondButton = downloadButtons.nth(1);
    const isSecondDisabled = await secondButton.isDisabled();
    if (isSecondDisabled) {
        logger.info('Other download buttons correctly disabled during report generation');
    }
    const csvEditorDialog = page.getByRole('dialog', {
        name: 'Edit CSV Data',
    });
    const dialogVisible = await csvEditorDialog
        .waitFor({ state: 'visible', timeout: 30000 })
        .then(() => true)
        .catch(() => false);
    if (dialogVisible) {
        await csvEditorDialog.getByRole('button', { name: 'Close' }).first().click();
    }
}
// ============================================
// Combined Recommendation Reports Test Helpers
// ============================================
async function shouldShowCombiningReportsDialog(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await test$1.expect(dataReportsTab).toBeVisible({ timeout: 60000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const combinedReportCard = page.locator('[data-testid="combined-recommendation-report-card"]');
    const isVisible = await combinedReportCard
        .first()
        .isVisible()
        .catch(() => false);
    if (!isVisible) {
        logger.info('No combined recommendation report cards found - feature may not be enabled');
        test$1.skip();
        return;
    }
    const downloadButton = combinedReportCard
        .first()
        .getByRole('button', { name: 'Download report' });
    await downloadButton.click();
    const combiningDialog = page.locator('[data-testid="combining-reports-dialog"]');
    await test$1.expect(combiningDialog).toBeVisible({ timeout: 30000 });
    await test$1.expect(combiningDialog.getByText('Combining Reports')).toBeVisible();
    await test$1.expect(combiningDialog.getByText('Loading & combining recommendations data')).toBeVisible();
    logger.info('Combining reports dialog displayed successfully');
}
async function shouldCancelCombiningReports(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await test$1.expect(dataReportsTab).toBeVisible({ timeout: 60000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const combinedReportCard = page.locator('[data-testid="combined-recommendation-report-card"]');
    const isVisible = await combinedReportCard
        .first()
        .isVisible()
        .catch(() => false);
    if (!isVisible) {
        logger.info('No combined recommendation report cards found - skipping');
        test$1.skip();
        return;
    }
    const downloadButton = combinedReportCard
        .first()
        .getByRole('button', { name: 'Download report' });
    await downloadButton.click();
    const combiningDialog = page.locator('[data-testid="combining-reports-dialog"]');
    await test$1.expect(combiningDialog).toBeVisible({ timeout: 30000 });
    const cancelButton = page.locator('[data-testid="cancel-combining-button"]');
    await test$1.expect(cancelButton).toBeVisible();
    await cancelButton.click();
    await test$1.expect(combiningDialog).not.toBeVisible({ timeout: 5000 });
    logger.info('Successfully cancelled combining reports');
}
async function shouldHaveCombinedReportDataTestIds(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await test$1.expect(dataReportsTab).toBeVisible({ timeout: 60000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const combinedReportCards = page.locator('[data-testid="combined-recommendation-report-card"]');
    const count = await combinedReportCards.count();
    if (count === 0) {
        logger.info('No combined recommendation report cards found - feature may not be enabled');
        test$1.skip();
        return;
    }
    for (let i = 0; i < count; i++) {
        const card = combinedReportCards.nth(i);
        await test$1.expect(card).toBeVisible();
        await test$1.expect(card.getByRole('button', { name: 'Download report' })).toBeVisible();
    }
    logger.info(`Found ${count} report cards with combined recommendation data-testid`);
}
async function shouldCombineRecommendationReports(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await test$1.expect(dataReportsTab).toBeVisible({ timeout: 60000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const combinedReportCard = page.locator('[data-testid="combined-recommendation-report-card"]');
    const isVisible = await combinedReportCard
        .first()
        .isVisible()
        .catch(() => false);
    if (!isVisible) {
        logger.info('No combined recommendation report cards found - skipping');
        test$1.skip();
        return;
    }
    const downloadButton = combinedReportCard
        .first()
        .getByRole('button', { name: 'Download report' });
    await downloadButton.click();
    const combiningDialog = page.locator('[data-testid="combining-reports-dialog"]');
    const dialogOrEditor = await Promise.race([
        combiningDialog.waitFor({ state: 'visible', timeout: 10000 }).then(() => 'dialog'),
        page
            .getByRole('dialog', { name: 'Edit CSV Data' })
            .waitFor({ state: 'visible', timeout: 60000 })
            .then(() => 'editor'),
    ]).catch(() => null);
    if (dialogOrEditor === 'dialog') {
        await test$1.expect(combiningDialog).not.toBeVisible({ timeout: 120000 });
    }
    const csvEditorDialog = page.getByRole('dialog', { name: 'Edit CSV Data' });
    await test$1.expect(csvEditorDialog).toBeVisible({ timeout: 120000 });
    const table = csvEditorDialog.getByRole('table', { name: 'CSV data table' });
    await test$1.expect(table).toBeVisible();
    const headerCells = table.locator('thead th');
    const headerCount = await headerCells.count();
    test$1.expect(headerCount).toBeGreaterThan(0);
    logger.info(`Combined reports opened in CSV editor with ${headerCount} columns`);
    await csvEditorDialog.getByRole('button', { name: 'Close' }).first().click();
    await test$1.expect(csvEditorDialog).not.toBeVisible({ timeout: 5000 });
}

/**
 * Create an environment configuration object from process.env with optional overrides.
 * Each app can call this with their specific defaults.
 */
function createEnvConfig(overrides) {
    return {
        skillHost: process.env.SKILLS_HOST || '',
        authHost: process.env.AUTH_HOST || '',
        analyticsHost: process.env.ANALYTICS_HOST || '',
        mentorHost: process.env.MENTOR_HOST || '',
        mentorNextjsHost: process.env.MENTOR_NEXTJS_HOST || '',
        authNextjsHost: process.env.AUTH_NEXTJS_HOST || '',
        playwrightUsername: process.env.PLAYWRIGHT_USERNAME || '',
        playwrightPassword: process.env.PLAYWRIGHT_PASSWORD || '',
        inviteUsername: process.env.INVITE_USERNAME || '',
        inviteUserPassword: process.env.INVITE_USER_PASSWORD || '',
        authFlow: process.env.AUTH_FLOW || 'username_password',
        authIdp: process.env.AUTH_IDP || '',
        enableStripeTest: process.env.ENABLE_STRIPE_TEST === 'true',
        iblMentorDisabledTabs: process.env.IBL_MENTOR_DISABLED_TABS || '',
        embedUrl: process.env.EMBED_URL || 'https://conradmugabe.vercel.app/',
        wcagRange: process.env.WCAG_RANGE || '',
        ...overrides,
    };
}

/**
 * Build the expected report download page URL.
 * Format: <baseUrl>/reports/<platformKey>/<reportName>
 */
function buildReportUrl(baseUrl, platformKey, reportName) {
    const base = baseUrl.replace(/\/$/, '');
    return `${base}/reports/${platformKey}/${reportName}`;
}
/**
 * Parse platform_key and report_name from the current page URL.
 * Expected format: .../reports/<platform_key>/<report_name>
 */
function parseReportUrlParams(url) {
    const match = url.match(/\/reports\/([^/]+)\/([^/]+)/);
    if (!match)
        return null;
    return { platformKey: match[1], reportName: match[2] };
}
/**
 * Navigate to the report download page and verify the URL matches the expected format.
 */
async function navigateToReportDownload(page, options) {
    const { baseUrl, platformKey, reportName, timeout = 60000 } = options;
    const url = buildReportUrl(baseUrl, platformKey, reportName);
    logger.info(`Navigating to report download page: ${url}`);
    await page.goto(url);
    await page.waitForLoadState('domcontentloaded', { timeout });
    // Verify URL matches expected format
    test$1.expect(page.url()).toContain(`/reports/${platformKey}/${reportName}`);
}
/**
 * Verify the report download page is showing the "Preparing" phase.
 */
async function verifyPreparingPhase(page, options) {
    var _a;
    const timeout = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : 10000;
    logger.info('Verifying report download page is in preparing phase');
    await test$1.expect(page.getByText('Preparing your report')).toBeVisible({ timeout });
    await test$1.expect(page.getByText('Fetching report details, this will only take a moment...')).toBeVisible({ timeout });
}
/**
 * Wait for and verify the report transitions to the "downloading" phase.
 */
async function verifyDownloadingPhase(page, options) {
    var _a;
    const timeout = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : 60000;
    logger.info('Waiting for report download to start');
    await test$1.expect(page.getByText('Report download started')).toBeVisible({ timeout });
    await test$1.expect(page.getByText('Your download should begin automatically.')).toBeVisible({
        timeout,
    });
    await test$1.expect(page.getByText('Click here to download if not started')).toBeVisible({ timeout });
}
/**
 * Wait for and verify the report transitions to the "done" phase.
 */
async function verifyDonePhase(page, options) {
    var _a;
    const timeout = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : 60000;
    logger.info('Waiting for report download to complete');
    await test$1.expect(page.getByText('Report downloaded')).toBeVisible({ timeout });
    await test$1.expect(page.getByText('Your report has been downloaded successfully.')).toBeVisible({
        timeout,
    });
    await test$1.expect(page.getByText('Download Again')).toBeVisible({ timeout });
}
/**
 * Wait for and verify the report transitions to the "error" phase.
 */
async function verifyErrorPhase(page, options) {
    var _a;
    const timeout = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : 60000;
    logger.info('Verifying report download error state');
    await test$1.expect(page.getByText('Download failed')).toBeVisible({ timeout });
    await test$1.expect(page.getByText('Something went wrong. Please try again later.')).toBeVisible({
        timeout,
    });
}
/**
 * Verify the "Back Home" button is visible and click it.
 */
async function clickBackHome(page) {
    logger.info('Clicking Back Home button');
    const backButton = page.getByRole('button', { name: 'Back Home' });
    await test$1.expect(backButton).toBeVisible();
    await backButton.click();
}
/**
 * Click the "Download Again" button (only visible in done phase).
 * Skips silently if the button is not visible.
 */
async function clickDownloadAgain(page) {
    logger.info('Clicking Download Again button');
    const button = page.getByRole('button', { name: 'Download Again' });
    const isVisible = await button.isVisible().catch(() => false);
    if (!isVisible) {
        logger.info('Download Again button not visible, skipping');
        return false;
    }
    await button.click();
    return true;
}
/**
 * Click the manual download link (only visible in downloading phase).
 */
async function clickManualDownloadLink(page) {
    logger.info('Clicking manual download link');
    const link = page.getByText('Click here to download if not started');
    await test$1.expect(link).toBeVisible();
    await link.click();
}
/**
 * Full flow: navigate to report download page and wait for download to complete.
 * Falls back to verifying the error phase if the download does not succeed.
 * Returns the resulting phase: 'done' or 'error'.
 */
async function waitForReportDownload(page, options) {
    var _a;
    await navigateToReportDownload(page, options);
    await verifyPreparingPhase(page);
    const timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 60000;
    // Wait for either the done or error phase to appear
    const result = await Promise.race([
        page
            .getByText('Report downloaded')
            .waitFor({ state: 'visible', timeout })
            .then(() => 'done'),
        page
            .getByText('Download failed')
            .waitFor({ state: 'visible', timeout })
            .then(() => 'error'),
    ]);
    if (result === 'done') {
        logger.info('Report download completed successfully');
        await verifyDonePhase(page, { timeout: 5000 });
    }
    else {
        logger.warn('Report download failed, verifying error state');
        await verifyErrorPhase(page, { timeout: 5000 });
    }
    return result;
}

/** Extract browser key from device name (e.g., 'Desktop Chrome' -> 'chrome') */
function getBrowserKey(deviceName) {
    return deviceName.toLowerCase().replace(/^desktop\s+/, '');
}
/**
 * Generate per-browser setup projects for Playwright authentication.
 * Each browser gets its own auth setup project (setup-chrome, setup-firefox, etc.)
 */
function generateBrowserSetupProjects(devicesToUse) {
    return devicesToUse.map((device) => {
        const browserKey = getBrowserKey(device);
        return {
            name: `setup-${browserKey}`,
            testMatch: /auth\.setup\.ts/,
            use: {
                ...test$1.devices[device],
            },
        };
    });
}
/**
 * Generate a Playwright project config for a platform + device combination.
 */
function generateProjectConfig(platform, deviceName, dependencies = [], otherTestMatch = ['*.common.spec.ts'], testIgnoreMatch = []) {
    const deviceSuffix = deviceName.toLowerCase().replace(/\s+/g, '-');
    const browserKey = getBrowserKey(deviceName);
    // Map generic 'setup' dependency to browser-specific setup
    const updatedDependencies = dependencies.map((dep) => dep === 'setup' ? `setup-${browserKey}` : dep);
    return {
        name: `${platform}-${deviceSuffix}`,
        testMatch: [`**${platform}/*.spec.ts`, ...otherTestMatch],
        use: {
            ...test$1.devices[deviceName],
            storageState: `playwright/.auth/user-${browserKey}.json`,
        },
        dependencies: [...updatedDependencies],
        testIgnore: [...testIgnoreMatch],
    };
}
/**
 * Create a complete Playwright configuration for an IBL.ai application.
 *
 * Usage:
 * ```ts
 * import { createPlaywrightConfig } from '@iblai/iblai-js/playwright';
 *
 * export default createPlaywrightConfig({
 *   platforms: [
 *     { name: 'skills', dependencies: ['setup'], otherTestMatch: ['**skills/*\/*.spec.ts'] },
 *   ],
 * });
 * ```
 */
function createPlaywrightConfig(options) {
    const { testDir = './tests', devices: devicesToUse = ['Desktop Chrome', 'Desktop Firefox', 'Desktop Safari', 'Desktop Edge'], platforms, customReporterPath = './custom-reporter', extraProjects = [], } = options;
    const browserSetupProjects = generateBrowserSetupProjects(devicesToUse);
    const platformProjects = [];
    for (const platform of platforms) {
        const platformDevices = platform.devices || devicesToUse;
        for (const device of platformDevices) {
            platformProjects.push(generateProjectConfig(platform.name, device, platform.dependencies || [], platform.otherTestMatch || ['*.common.spec.ts'], platform.testIgnore || []));
        }
    }
    // Generate extra projects (cleanup, public-views, etc.)
    const extraProjectConfigs = [];
    for (const extra of extraProjects) {
        const extraDevices = extra.devices || devicesToUse;
        for (const device of extraDevices) {
            const deviceSuffix = device.toLowerCase().replace(/\s+/g, '-');
            const browserKey = getBrowserKey(device);
            const updatedDeps = extra.dependencies.map((dep) => {
                // If dependency references a platform, expand to device-specific name
                const matchingPlatform = platforms.find((p) => p.name === dep);
                if (matchingPlatform) {
                    return `${dep}-${deviceSuffix}`;
                }
                return dep === 'setup' ? `setup-${browserKey}` : dep;
            });
            extraProjectConfigs.push({
                name: `${extra.name}-${deviceSuffix}`,
                testMatch: extra.testMatch,
                use: {
                    ...test$1.devices[device],
                    storageState: `playwright/.auth/user-${browserKey}.json`,
                },
                dependencies: updatedDeps,
                testIgnore: [],
            });
        }
    }
    return test$1.defineConfig({
        testDir,
        fullyParallel: true,
        forbidOnly: !!process.env.CI,
        retries: process.env.CI ? 3 : 0,
        workers: process.env.CI ? 1 : undefined,
        projects: [...browserSetupProjects, ...platformProjects, ...extraProjectConfigs],
        reporter: [
            [
                'html',
                {
                    outputFolder: process.env.HTML_REPORTER_OUTPUT_DIR || 'playwright-report',
                    open: process.env.CI ? 'never' : 'on-failure',
                },
            ],
            process.env.CI ? ['list', { printSteps: true }] : [customReporterPath],
            ...(process.env.CI ? [['json', { outputFile: '/app/test-results.json' }]] : []),
        ],
        timeout: process.env.CI ? 300000 : 200000,
        use: {
            headless: true,
            trace: process.env.CI ? 'retain-on-failure' : 'off',
            launchOptions: {
                slowMo: process.env.CI ? 0 : 1000,
            },
            ...(process.env.CI && {
                video: 'off',
                screenshot: 'off',
                actionTimeout: 30000,
                navigationTimeout: 30000,
            }),
        },
    });
}

Object.defineProperty(exports, "expect", {
    enumerable: true,
    get: function () { return test$1.expect; }
});
exports.AuthFlowBuilder = AuthFlowBuilder;
exports.CustomReporter = CustomReporter;
exports.MailsacClient = MailsacClient;
exports.buildReportUrl = buildReportUrl;
exports.canChatWithEmbedMentor = canChatWithEmbedMentor;
exports.checkAdminStatus = checkAdminStatus;
exports.clickBackHome = clickBackHome;
exports.clickDownloadAgain = clickDownloadAgain;
exports.clickManualDownloadLink = clickManualDownloadLink;
exports.closeWithEsc = closeWithEsc;
exports.createAuthSetup = createAuthSetup;
exports.createEnvConfig = createEnvConfig;
exports.createPlaywrightConfig = createPlaywrightConfig;
exports.expectNoAccessibilityViolations = expectNoAccessibilityViolations;
exports.expectNoAccessibilityViolationsOnDialogs = expectNoAccessibilityViolationsOnDialogs;
exports.generateBrowserSetupProjects = generateBrowserSetupProjects;
exports.generateProjectConfig = generateProjectConfig;
exports.getBrowserKey = getBrowserKey;
exports.getMentorIdFromUrl = getMentorIdFromUrl;
exports.inviteUserTest = inviteUserTest;
exports.isFirefox = isFirefox;
exports.isJSON = isJSON;
exports.logger = logger;
exports.loginWithEmailAndPassword = loginWithEmailAndPassword;
exports.loginWithMicrosoftIdp = loginWithMicrosoftIdp;
exports.navigateToAccountComponent = navigateToAccountComponent;
exports.navigateToDataReports = navigateToDataReports;
exports.navigateToReportDownload = navigateToReportDownload;
exports.parseReportUrlParams = parseReportUrlParams;
exports.reliableClick = reliableClick;
exports.reliableFill = reliableFill;
exports.retry = retry;
exports.safeWaitForURL = safeWaitForURL;
exports.selectDateFromCalendar = selectDateFromCalendar;
exports.shouldAddNewRowWhenClickingAddRowButton = shouldAddNewRowWhenClickingAddRowButton;
exports.shouldAllowEditingCellValuesInCSVEditor = shouldAllowEditingCellValuesInCSVEditor;
exports.shouldCancelCombiningReports = shouldCancelCombiningReports;
exports.shouldCloseCSVEditorWhenClickingCloseButton = shouldCloseCSVEditorWhenClickingCloseButton;
exports.shouldCloseCSVEditorWithoutSavingWhenClickingCancel = shouldCloseCSVEditorWithoutSavingWhenClickingCancel;
exports.shouldCombineRecommendationReports = shouldCombineRecommendationReports;
exports.shouldDirectlyDownloadChatHistoryReportWithoutCSVEditor = shouldDirectlyDownloadChatHistoryReportWithoutCSVEditor;
exports.shouldDisableOtherDownloadButtonsWhileGeneratingReport = shouldDisableOtherDownloadButtonsWhileGeneratingReport;
exports.shouldDisplayCSVInEditableTableFormat = shouldDisplayCSVInEditableTableFormat;
exports.shouldDisplayReportCards = shouldDisplayReportCards;
exports.shouldHaveCombinedReportDataTestIds = shouldHaveCombinedReportDataTestIds;
exports.shouldOpenCSVEditorDialog = shouldOpenCSVEditorDialog;
exports.shouldOpenCSVEditorForUserMetadataReport = shouldOpenCSVEditorForUserMetadataReport;
exports.shouldSaveEditedCSVAndTriggerDownload = shouldSaveEditedCSVAndTriggerDownload;
exports.shouldShowCombiningReportsDialog = shouldShowCombiningReportsDialog;
exports.shouldVerifyCSVEditorDialogAccessibility = shouldVerifyCSVEditorDialogAccessibility;
exports.signUpWithEmailAndPassword = signUpWithEmailAndPassword;
exports.test = test;
exports.verifyDonePhase = verifyDonePhase;
exports.verifyDownloadingPhase = verifyDownloadingPhase;
exports.verifyErrorPhase = verifyErrorPhase;
exports.verifyPreparingPhase = verifyPreparingPhase;
exports.waitForDialogReady = waitForDialogReady;
exports.waitForElementStable = waitForElementStable;
exports.waitForPageLoad = waitForPageLoad;
exports.waitForPageReady = waitForPageReady;
exports.waitForReportDownload = waitForReportDownload;
//# sourceMappingURL=index.cjs.map
