mirror of
https://github.com/hanzoai/team.git
synced 2026-11-01 07:55:31 +00:00
* Switch Cypress to use shared ESLint config * Run --fix in Cypress * Manually fix remaining lint issues in Cypress * Switch Playwright to use shared ESLint config * Run --fix in Playwright * Manually fix remaining lint issues in Playwright * Install and cache web app deps during Cypress CI builds This also caches the types and client package. That isn't needed currently since it uses prepackaged versions of those, but I imagine we might change that at some point. * Run e2e-tests-check when ESLint plugin is updated * Change E2E test GHA caching to cache all of web app node_modules * Fix mismatch between cache save and restore * Try bumping cache keys * Copy step to install dependencies to server.run_cypress.sh I don't know how this must've worked before, but if this fixes the issue, it seems like neither Cypress nor Playwright actually use the cached depenendencies. * Try disabling caching entirely for Cypress tests * Try bypassing makefile? * Try also manually building dependencies in run_specs.sh I don't know why this appears to duplicate run_cypress.sh and run_playwright.sh, both of which are called run_test.sh which might not be used any more as best I can tell. * Try installing the web app dependencies in yet another place * Disable the extra steps in server.prepare.sh specifically for Cypress * Revert changes to update cache key and disable web app depenedency cache on Cypress builds
304 lines
9.6 KiB
TypeScript
304 lines
9.6 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import type {Page, Route} from '@playwright/test';
|
|
|
|
interface MockTranslateRequest {
|
|
q?: string;
|
|
text?: string;
|
|
source?: string;
|
|
target?: string;
|
|
}
|
|
|
|
interface MockTranslateResponse {
|
|
translatedText: string;
|
|
detectedLanguage: {language: string; confidence: number};
|
|
}
|
|
|
|
/**
|
|
* Tracks the source language for mock translation responses
|
|
* Used to simulate language detection behavior
|
|
*/
|
|
let mockSourceLanguage = 'es';
|
|
|
|
/**
|
|
* Simple language detection from text content
|
|
* Simulates how real LibreTranslate detects language from actual message text
|
|
*/
|
|
function detectLanguageFromText(text: string): string {
|
|
if (!text) {
|
|
return mockSourceLanguage;
|
|
}
|
|
|
|
const lowerText = text.toLowerCase();
|
|
|
|
// Spanish patterns
|
|
const spanishIndicators = [
|
|
'el ',
|
|
'la ',
|
|
'de ',
|
|
'que ',
|
|
'para ',
|
|
'con ',
|
|
'una ',
|
|
'este ',
|
|
'está',
|
|
'muy',
|
|
'en ',
|
|
'aquí',
|
|
'gracias',
|
|
'hola',
|
|
'adiós',
|
|
];
|
|
const spanishMatches = spanishIndicators.filter((indicator) => lowerText.includes(indicator)).length;
|
|
|
|
// English patterns
|
|
const englishIndicators = [
|
|
'the ',
|
|
'and ',
|
|
'is ',
|
|
'to ',
|
|
'of ',
|
|
'that ',
|
|
'this ',
|
|
'for ',
|
|
'with ',
|
|
'hello',
|
|
'thanks',
|
|
'please',
|
|
'translation',
|
|
];
|
|
const englishMatches = englishIndicators.filter((indicator) => lowerText.includes(indicator)).length;
|
|
|
|
// French patterns
|
|
const frenchIndicators = [
|
|
'le ',
|
|
'la ',
|
|
'de ',
|
|
'est ',
|
|
'que ',
|
|
'pour ',
|
|
'avec ',
|
|
'un ',
|
|
'une ',
|
|
'ça',
|
|
'bonjour',
|
|
'merci',
|
|
];
|
|
const frenchMatches = frenchIndicators.filter((indicator) => lowerText.includes(indicator)).length;
|
|
|
|
// Return the language with most matches
|
|
if (spanishMatches > englishMatches && spanishMatches > frenchMatches) {
|
|
return 'es';
|
|
}
|
|
if (englishMatches > frenchMatches) {
|
|
return 'en';
|
|
}
|
|
if (frenchMatches > 0) {
|
|
return 'fr';
|
|
}
|
|
|
|
// Default to configured mock language
|
|
return mockSourceLanguage;
|
|
}
|
|
|
|
/**
|
|
* Set the source language that the mock will detect
|
|
* Useful for testing language detection and filtering logic
|
|
*/
|
|
export function setMockSourceLanguage(language: string): void {
|
|
mockSourceLanguage = language;
|
|
}
|
|
|
|
/**
|
|
* Get the current mock source language
|
|
*/
|
|
export function getMockSourceLanguage(): string {
|
|
return mockSourceLanguage;
|
|
}
|
|
|
|
/**
|
|
* Reset mock source language to default
|
|
*/
|
|
export function resetMockSourceLanguage(): void {
|
|
mockSourceLanguage = 'es';
|
|
}
|
|
|
|
/**
|
|
* Mock the autotranslation API route using Playwright's route interception.
|
|
* This replaces the need for an external mock server.
|
|
*
|
|
* Returns a `Disposable` to remove the mock routes. Use with `await using`
|
|
* for automatic cleanup or call `.dispose()` manually:
|
|
*
|
|
* ```ts
|
|
* await using mock = await mockAutotranslationRoute(page);
|
|
* ```
|
|
*
|
|
* @param page - Playwright Page object
|
|
* @param options - Optional configuration
|
|
* @param options.sourceLanguage - Initial detected source language (default: 'es')
|
|
* @param options.supportedLanguages - List of supported target languages (default: ['en', 'es', 'fr', 'de'])
|
|
*/
|
|
export async function mockAutotranslationRoute(
|
|
page: Page,
|
|
options?: {
|
|
sourceLanguage?: string;
|
|
supportedLanguages?: string[];
|
|
},
|
|
): Promise<{dispose(): Promise<void>; [Symbol.asyncDispose](): Promise<void>}> {
|
|
// Reset mockSourceLanguage to avoid state leakage between tests
|
|
mockSourceLanguage = options?.sourceLanguage || 'es';
|
|
|
|
const supportedLanguages = options?.supportedLanguages || ['en', 'es', 'fr', 'de'];
|
|
|
|
// Named handler so it can be passed to page.unroute() for cleanup
|
|
const translateHandler = async (route: Route): Promise<void> => {
|
|
const request = route.request();
|
|
const method = request.method();
|
|
|
|
if (method === 'POST') {
|
|
let postData: MockTranslateRequest;
|
|
try {
|
|
postData = (await request.postDataJSON()) as MockTranslateRequest;
|
|
} catch {
|
|
// If POST data is not JSON, try to parse as form-encoded data
|
|
const postDataBuffer = request.postData();
|
|
if (!postDataBuffer) {
|
|
await route.abort('failed');
|
|
return;
|
|
}
|
|
// Parse form-encoded data (application/x-www-form-urlencoded)
|
|
const formData = new URLSearchParams(postDataBuffer.toString());
|
|
postData = {
|
|
q: formData.get('q') || undefined,
|
|
text: formData.get('text') || undefined,
|
|
source: formData.get('source') || undefined,
|
|
target: formData.get('target') || undefined,
|
|
};
|
|
}
|
|
|
|
const textToTranslate = postData.q || postData.text || '';
|
|
// When source is empty or "auto", detect language from text (simulates real LibreTranslate)
|
|
// Otherwise use the provided source language
|
|
const sourceLanguage =
|
|
postData.source && postData.source !== 'auto'
|
|
? postData.source
|
|
: detectLanguageFromText(textToTranslate);
|
|
const targetLanguage = postData.target || 'en';
|
|
|
|
// Validate target language is supported
|
|
if (!supportedLanguages.includes(targetLanguage)) {
|
|
await route.fulfill({
|
|
status: 400,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
error: `Target language '${targetLanguage}' is not supported`,
|
|
}),
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Mock response: only "translate" if source differs from target
|
|
let translatedText = textToTranslate;
|
|
const shouldTranslate = sourceLanguage !== targetLanguage;
|
|
|
|
if (shouldTranslate) {
|
|
// Prepend language code to simulate translation
|
|
translatedText = `[${targetLanguage}] ${textToTranslate}`;
|
|
}
|
|
|
|
const response: MockTranslateResponse = {
|
|
translatedText,
|
|
detectedLanguage: {
|
|
language: sourceLanguage,
|
|
confidence: 0.95,
|
|
},
|
|
};
|
|
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(response),
|
|
});
|
|
} else if (method === 'GET') {
|
|
// Handle GET requests (e.g., /translate?q=hello&source=es&target=en)
|
|
const url = new URL(request.url());
|
|
const textToTranslate = url.searchParams.get('q') || '';
|
|
const sourceParam = url.searchParams.get('source');
|
|
// When source is empty or "auto", detect language from text (simulates real LibreTranslate)
|
|
const sourceLanguage =
|
|
sourceParam && sourceParam !== 'auto' ? sourceParam : detectLanguageFromText(textToTranslate);
|
|
const targetLanguage = url.searchParams.get('target') || 'en';
|
|
|
|
if (!supportedLanguages.includes(targetLanguage)) {
|
|
await route.fulfill({
|
|
status: 400,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
error: `Target language '${targetLanguage}' is not supported`,
|
|
}),
|
|
});
|
|
return;
|
|
}
|
|
|
|
let translatedText = textToTranslate;
|
|
const shouldTranslate = sourceLanguage !== targetLanguage;
|
|
|
|
if (shouldTranslate) {
|
|
translatedText = `[${targetLanguage}] ${textToTranslate}`;
|
|
}
|
|
|
|
const response: MockTranslateResponse = {
|
|
translatedText,
|
|
detectedLanguage: {
|
|
language: sourceLanguage,
|
|
confidence: 0.95,
|
|
},
|
|
};
|
|
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(response),
|
|
});
|
|
} else {
|
|
await route.abort('failed');
|
|
}
|
|
};
|
|
|
|
// Named handler so it can be passed to page.unroute() for cleanup
|
|
const detectHandler = async (route: Route): Promise<void> => {
|
|
// Language detection is mocked to always return the configured source language
|
|
// regardless of the input text
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
result: [
|
|
{
|
|
language: mockSourceLanguage,
|
|
confidence: 0.95,
|
|
},
|
|
],
|
|
detectedLanguage: {language: mockSourceLanguage, confidence: 0.95},
|
|
}),
|
|
});
|
|
};
|
|
|
|
// Mock LibreTranslate API endpoints
|
|
await page.route('**/api/translate', translateHandler);
|
|
await page.route('**/api/detect', detectHandler);
|
|
|
|
return {
|
|
async dispose() {
|
|
await page.unroute('**/api/translate', translateHandler);
|
|
await page.unroute('**/api/detect', detectHandler);
|
|
},
|
|
async [Symbol.asyncDispose]() {
|
|
await page.unroute('**/api/translate', translateHandler);
|
|
await page.unroute('**/api/detect', detectHandler);
|
|
},
|
|
};
|
|
}
|