mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 16:09:05 +00:00
279 lines
11 KiB
JavaScript
279 lines
11 KiB
JavaScript
const { chromium } = require('@playwright/test');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const logsdata = require("../../../test-data/logs_data.json");
|
|
const testLogger = require('./test-logger.js');
|
|
const metricsIngestion = require('./metrics-ingestion.js');
|
|
const { ingestTraces } = require('./trace-ingestion.js');
|
|
const { ingestRumErrors } = require('./rum-error-ingestion.js');
|
|
const { ingestBase } = require('./ingest-url.js');
|
|
|
|
/**
|
|
* Global setup for all tests - handles authentication and test data ingestion
|
|
* This runs once before all tests start
|
|
*/
|
|
async function globalSetup() {
|
|
testLogger.info('Starting global setup for all tests');
|
|
|
|
// Create auth storage directory
|
|
const authDir = path.join(__dirname, 'auth');
|
|
if (!fs.existsSync(authDir)) {
|
|
fs.mkdirSync(authDir, { recursive: true });
|
|
}
|
|
const authFile = path.join(authDir, 'user.json');
|
|
|
|
// Launch browser and create context
|
|
const browser = await chromium.launch({
|
|
args: process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : []
|
|
});
|
|
const context = await browser.newContext({
|
|
viewport: { width: 1500, height: 1024 }
|
|
});
|
|
const page = await context.newPage();
|
|
|
|
try {
|
|
testLogger.info('Performing global login authentication');
|
|
|
|
// Navigate to login page with org identifier to establish correct org context
|
|
const loginUrl = `${process.env["ZO_BASE_URL"]}?org_identifier=${process.env["ORGNAME"]}`;
|
|
await page.goto(loginUrl);
|
|
testLogger.debug('Navigated to base URL with org', { url: loginUrl });
|
|
|
|
await page.waitForLoadState('domcontentloaded');
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Debug: Log current URL and page title
|
|
const currentUrl = page.url();
|
|
const pageTitle = await page.title();
|
|
testLogger.debug('Page loaded', { currentUrl, pageTitle });
|
|
|
|
// Handle internal user login if needed — data-test only, no text matching.
|
|
const internalUserButton = page.locator('[data-test="login-as-internal-user"]');
|
|
if (await internalUserButton.count()) {
|
|
if (await internalUserButton.first().isVisible().catch(() => false)) {
|
|
await internalUserButton.first().click();
|
|
testLogger.debug('Clicked internal user login button');
|
|
await page.waitForLoadState('domcontentloaded');
|
|
}
|
|
}
|
|
|
|
// Fill login credentials — data-test only. OInput's WRAPPER carries
|
|
// `data-test="<name>"`; the actual <input>/<textarea> element inside it
|
|
// carries `data-test="<name>-field"`. Playwright's fill() requires a
|
|
// fillable element so we target the -field suffix directly.
|
|
const userIdField = page.locator('[data-test="login-user-id-field"]');
|
|
const passwordField = page.locator('[data-test="login-password-field"]');
|
|
const signInButton = page.locator('[data-test="login-sign-in"]');
|
|
|
|
testLogger.debug('Waiting for login fields to appear...');
|
|
try {
|
|
await userIdField.waitFor({ state: 'visible', timeout: 15000 });
|
|
} catch (e) {
|
|
await page.screenshot({ path: path.join(__dirname, 'debug-login-page.png') });
|
|
const html = await page.content();
|
|
testLogger.error('Login field not found', {
|
|
url: page.url(),
|
|
bodyPreview: html?.substring(0, 500),
|
|
});
|
|
throw new Error(`Login page did not load correctly. Current URL: ${page.url()}`);
|
|
}
|
|
await userIdField.fill(process.env["ZO_ROOT_USER_EMAIL"]);
|
|
|
|
await passwordField.waitFor({ state: 'visible' });
|
|
await passwordField.fill(process.env["ZO_ROOT_USER_PASSWORD"]);
|
|
|
|
await signInButton.waitFor({ state: 'visible' });
|
|
await signInButton.click();
|
|
|
|
testLogger.debug('Login credentials submitted');
|
|
|
|
// Wait for login to complete — look for navigation or success indicators.
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
|
|
// Verify login success by checking for the main nav rail — the current
|
|
// rail no longer renders per-route `menu-link-*` items (see #13659, which
|
|
// made the same fix in enhanced-baseFixtures.js).
|
|
// Deployed hosts take ~13s after sign-in to mount the shell, so 10s failed every run there.
|
|
await page.locator('[data-test="navbar-main-nav"]').waitFor({
|
|
state: 'visible',
|
|
timeout: 30000,
|
|
});
|
|
|
|
testLogger.info('Global login authentication successful');
|
|
|
|
// Save authentication state
|
|
await context.storageState({ path: authFile });
|
|
testLogger.info('Authentication state saved successfully', { authFile });
|
|
|
|
// Skip data ingestion for cleanup-only runs
|
|
// Use precise regex to match only cleanup.spec.js (not some-cleanup.spec.js or cleanup.spec-backup.js)
|
|
const isCleanupOnly = process.argv.some(arg => /cleanup\.spec\.(js|ts)$/.test(arg));
|
|
if (isCleanupOnly || process.env.SKIP_INGESTION === 'true') {
|
|
testLogger.info('Skipping data ingestion (cleanup-only run or SKIP_INGESTION=true)');
|
|
} else {
|
|
// Perform global test data ingestion
|
|
testLogger.info('Starting global test data ingestion');
|
|
await performGlobalIngestion(page);
|
|
testLogger.info('Global test data ingestion completed');
|
|
|
|
// Skip global metrics ingestion - it's now handled in test-specific beforeAll hooks
|
|
// This avoids 404 errors when OpenObserve metrics endpoint isn't ready during global setup
|
|
// await performMetricsIngestion();
|
|
testLogger.debug('Metrics ingestion skipped in global setup - will be handled in test beforeAll hooks');
|
|
|
|
// Perform trace data ingestion
|
|
testLogger.info('Starting trace data ingestion');
|
|
await ingestTraces(page, 20); // Ingest 20 test traces
|
|
testLogger.info('Trace data ingestion completed');
|
|
|
|
// Perform RUM error data ingestion
|
|
testLogger.info('Starting RUM error data ingestion');
|
|
await ingestRumErrors(page, 3); // Ingest 3 different error types
|
|
testLogger.info('RUM error data ingestion completed');
|
|
|
|
// Saved again as a browser that has seen the org with data holds it, so the one-time first-data banner never lands mid-test.
|
|
await recordFirstDataSeen(page);
|
|
await context.storageState({ path: authFile });
|
|
testLogger.info('Storage state saved after the first visit with data', { authFile });
|
|
}
|
|
|
|
} catch (error) {
|
|
testLogger.error('Global setup failed', { error: error.message, stack: error.stack });
|
|
throw error;
|
|
} finally {
|
|
await context.close();
|
|
await browser.close();
|
|
testLogger.debug('Browser closed after global setup');
|
|
}
|
|
|
|
testLogger.info('Global setup completed successfully - ready for test execution');
|
|
}
|
|
|
|
/**
|
|
* Reloads the app until the first-data notice records that the org has data.
|
|
* @param {import('@playwright/test').Page} page
|
|
*/
|
|
async function recordFirstDataSeen(page) {
|
|
const key = `o2.onboarding.firstData.${process.env["ORGNAME"]}`;
|
|
for (let attempt = 1; attempt <= 5; attempt++) {
|
|
await page.reload({ waitUntil: 'domcontentloaded' });
|
|
const recorded = await page
|
|
.waitForFunction((k) => JSON.parse(localStorage.getItem(k) || 'null')?.hadData === true, key, { timeout: 15000 })
|
|
.then(() => true)
|
|
.catch(() => false);
|
|
if (recorded) {
|
|
testLogger.info('First-data visit recorded', { attempt });
|
|
return;
|
|
}
|
|
}
|
|
testLogger.warn('First-data visit not recorded; tests may still see the first-data banner', { key });
|
|
}
|
|
|
|
/**
|
|
* Performs global test data ingestion
|
|
* @param {import('@playwright/test').Page} page
|
|
*/
|
|
async function performGlobalIngestion(page) {
|
|
const orgId = process.env["ORGNAME"];
|
|
const streamName = "e2e_automate";
|
|
const basicAuthCredentials = Buffer.from(
|
|
`${process.env["ZO_ROOT_USER_EMAIL"]}:${process.env["ZO_ROOT_USER_PASSWORD"]}`
|
|
).toString('base64');
|
|
|
|
const headers = {
|
|
"Authorization": `Basic ${basicAuthCredentials}`,
|
|
"Content-Type": "application/json",
|
|
};
|
|
|
|
const baseUrl = (process.env.INGESTION_URL || process.env.ZO_BASE_URL || 'http://localhost:5080').replace(/\/+$/, '');
|
|
let response;
|
|
try {
|
|
response = await page.request.post(`${ingestBase(baseUrl)}/api/${orgId}/${streamName}/_json`, {
|
|
headers: headers,
|
|
data: logsdata
|
|
});
|
|
} catch (requestError) {
|
|
testLogger.error('Data ingestion request failed', { error: requestError.message });
|
|
throw new Error(`Data ingestion request failed: ${requestError.message}`);
|
|
}
|
|
|
|
const responseStatus = response.status();
|
|
let responseData = null;
|
|
const contentType = response.headers()['content-type'] || '';
|
|
if (contentType.includes('application/json')) {
|
|
try {
|
|
const text = await response.text();
|
|
responseData = text ? JSON.parse(text) : null;
|
|
} catch (e) {
|
|
responseData = { error: 'Failed to parse JSON response', text: await response.text().catch(() => '') };
|
|
}
|
|
} else {
|
|
responseData = { text: await response.text().catch(() => '') };
|
|
}
|
|
|
|
testLogger.debug('Data ingestion API response received', {
|
|
status: responseStatus,
|
|
orgId,
|
|
streamName,
|
|
data: responseData
|
|
});
|
|
|
|
if (responseStatus !== 200) {
|
|
testLogger.error('Data ingestion failed', {
|
|
status: responseStatus,
|
|
response: responseData,
|
|
url: `${ingestBase(baseUrl)}/api/${orgId}/${streamName}/_json`,
|
|
orgId,
|
|
streamName
|
|
});
|
|
throw new Error(`Data ingestion failed with status: ${responseStatus}. Response: ${JSON.stringify(responseData)}`);
|
|
}
|
|
|
|
testLogger.info('Global data ingestion successful', {
|
|
status: responseStatus,
|
|
orgId,
|
|
streamName,
|
|
responseData: responseData
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Performs global metrics data ingestion
|
|
*/
|
|
async function performMetricsIngestion() {
|
|
try {
|
|
// Ingest test metrics data
|
|
const result = await metricsIngestion.ingestTestMetrics({
|
|
iterations: 20, // Send 20 batches of metrics for better test coverage
|
|
delay: 100, // 100ms between batches
|
|
useExternal: false // Use local OpenObserve instance
|
|
});
|
|
|
|
if (result.success) {
|
|
testLogger.info('Metrics ingestion successful', {
|
|
totalBatches: result.totalBatches,
|
|
successfulBatches: result.successfulBatches
|
|
});
|
|
} else {
|
|
testLogger.warn('Metrics ingestion completed with some failures', {
|
|
totalBatches: result.totalBatches,
|
|
successfulBatches: result.successfulBatches
|
|
});
|
|
}
|
|
|
|
// Also start continuous ingestion that will run during tests
|
|
// This runs in the background and doesn't block
|
|
metricsIngestion.ingestContinuously(120000, 10000).then(result => {
|
|
testLogger.debug('Background metrics ingestion completed', result);
|
|
}).catch(error => {
|
|
testLogger.error('Background metrics ingestion failed', { error: error.message });
|
|
});
|
|
|
|
} catch (error) {
|
|
testLogger.error('Metrics ingestion failed', { error: error.message });
|
|
// Don't fail the entire setup if metrics ingestion fails
|
|
// Tests can still run without metrics data
|
|
}
|
|
}
|
|
|
|
module.exports = globalSetup; |