@equallyze/wally-cypress 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Tipos e interfaces para o wally-cypress
3
+ */
4
+ /**
5
+ * Ambientes disponíveis para a API Wally
6
+ */
7
+ type WallyEnvironment = 'production' | 'staging' | 'development';
8
+ /**
9
+ * Configuração do plugin Wally Cypress
10
+ */
11
+ interface WallyConfig {
12
+ /**
13
+ * API Key da organização (obtida no painel Wally)
14
+ * @required
15
+ */
16
+ apiKey: string;
17
+ /**
18
+ * ID do projeto para associar as análises
19
+ * @required
20
+ */
21
+ projectId: string;
22
+ /**
23
+ * ID da norma de acessibilidade a ser utilizada
24
+ * @optional
25
+ */
26
+ normId?: string;
27
+ /**
28
+ * ID do quality gate para avaliação
29
+ * @optional
30
+ */
31
+ qualityGateId?: string;
32
+ /**
33
+ * Ambiente da API Wally
34
+ * @default 'production'
35
+ */
36
+ environment?: WallyEnvironment;
37
+ /**
38
+ * URL customizada da API (sobrescreve environment)
39
+ * @optional
40
+ */
41
+ apiUrl?: string;
42
+ /**
43
+ * Timeout em ms para requisições à API
44
+ * @default 30000
45
+ */
46
+ timeout?: number;
47
+ /**
48
+ * Se deve falhar o teste quando há erro de comunicação com Wally
49
+ * @default false
50
+ */
51
+ failOnError?: boolean;
52
+ }
53
+ /**
54
+ * Estado da sessão de navegação ativa
55
+ */
56
+ interface WallySession {
57
+ /** ID da sessão */
58
+ id: string;
59
+ /** ID da organização */
60
+ organizationId: string;
61
+ /** Status atual */
62
+ status: 'ACTIVE' | 'FINISHED' | 'ANALYZING' | 'COMPLETED' | 'FAILED';
63
+ /** Data/hora de início */
64
+ startedAt: Date;
65
+ /** Páginas capturadas */
66
+ pages: WallyPage[];
67
+ }
68
+ /**
69
+ * Página capturada durante a sessão
70
+ */
71
+ interface WallyPage {
72
+ /** ID da página no backend */
73
+ id?: string;
74
+ /** URL da página */
75
+ url: string;
76
+ /** Título/nome dado à página */
77
+ title: string;
78
+ /** Timestamp da captura */
79
+ timestamp: Date;
80
+ /** Tamanho do HTML em bytes */
81
+ contentSize?: number;
82
+ }
83
+ /**
84
+ * Resposta da finalização de sessão
85
+ */
86
+ interface FinishSessionResponse {
87
+ success: boolean;
88
+ sessionId: string;
89
+ status: string;
90
+ totalPages: number;
91
+ analysisId?: string;
92
+ message: string;
93
+ }
94
+ /**
95
+ * Configuração interna resolvida
96
+ */
97
+ interface ResolvedConfig extends Required<Omit<WallyConfig, 'apiUrl' | 'normId' | 'qualityGateId'>> {
98
+ apiUrl: string;
99
+ normId?: string;
100
+ qualityGateId?: string;
101
+ organizationId: string;
102
+ }
103
+ /**
104
+ * Extensão da interface Cypress para comandos customizados
105
+ */
106
+ declare global {
107
+ namespace Cypress {
108
+ interface Chainable {
109
+ /**
110
+ * Captura a página atual para análise de acessibilidade
111
+ *
112
+ * A sessão Wally é gerenciada automaticamente pelo plugin:
113
+ * - Iniciada no hook `before:run`
114
+ * - Finalizada no hook `after:run`
115
+ *
116
+ * @param pageName Nome descritivo da página (ex: "Homepage", "Checkout Step 1")
117
+ * @example cy.accessibility('Homepage')
118
+ */
119
+ accessibility(pageName: string): Chainable<WallyPage | null>;
120
+ }
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Plugin Cypress para integração com Wally
126
+ *
127
+ * Este módulo é executado no Node.js (setupNodeEvents) e gerencia:
128
+ * - Sessão de navegação
129
+ * - Comunicação com API Wally
130
+ * - Ciclo de vida dos testes
131
+ *
132
+ * @example
133
+ * // cypress.config.ts
134
+ * import { wallyPlugin } from '@equallyze/wally-cypress/plugin';
135
+ *
136
+ * export default defineConfig({
137
+ * e2e: {
138
+ * setupNodeEvents(on, config) {
139
+ * wallyPlugin(on, config, {
140
+ * apiKey: process.env.WALLY_API_KEY,
141
+ * projectId: process.env.WALLY_PROJECT_ID,
142
+ * });
143
+ * return config;
144
+ * },
145
+ * },
146
+ * });
147
+ */
148
+
149
+ /**
150
+ * Inicializa o plugin Wally
151
+ */
152
+ declare function initialize(config: WallyConfig): Promise<boolean>;
153
+ /**
154
+ * Inicia uma nova sessão de navegação
155
+ */
156
+ declare function startSession(baseUrl?: string): Promise<WallySession | null>;
157
+ /**
158
+ * Captura uma página e envia para o backend
159
+ */
160
+ declare function capturePage(pageName: string, url: string, html: string): Promise<WallyPage | null>;
161
+ /**
162
+ * Finaliza a sessão e inicia análise
163
+ */
164
+ declare function finishSession(): Promise<FinishSessionResponse | null>;
165
+ /**
166
+ * Obtém o estado atual da sessão
167
+ */
168
+ declare function getSession(): WallySession | null;
169
+ /**
170
+ * Obtém configuração atual
171
+ */
172
+ declare function getConfig(): ResolvedConfig | null;
173
+ /**
174
+ * Plugin principal para Cypress
175
+ *
176
+ * @param on - Objeto de eventos Cypress
177
+ * @param config - Configuração Cypress
178
+ * @param wallyConfig - Configuração do plugin Wally
179
+ */
180
+ declare function wallyPlugin(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions, wallyConfig: WallyConfig): void;
181
+
182
+ export { type WallyConfig as W, type WallyEnvironment as a, type WallyPage as b, type WallySession as c, capturePage as d, getSession as e, finishSession as f, getConfig as g, initialize as i, startSession as s, wallyPlugin as w };
@@ -0,0 +1 @@
1
+ export { d as capturePage, f as finishSession, g as getConfig, e as getSession, i as initialize, s as startSession, w as wallyPlugin } from './plugin-DE0EFpNI.mjs';
@@ -0,0 +1 @@
1
+ export { d as capturePage, f as finishSession, g as getConfig, e as getSession, i as initialize, s as startSession, w as wallyPlugin } from './plugin-DE0EFpNI.js';
package/dist/plugin.js ADDED
@@ -0,0 +1,363 @@
1
+ 'use strict';
2
+
3
+ var zlib = require('zlib');
4
+
5
+ // src/api-client.ts
6
+ var ENVIRONMENTS = {
7
+ production: "https://api.wally.equallyze.com/backend/services/v0.0.1",
8
+ staging: "https://api.stg.wally.equallyze.com/backend/services/v0.0.1",
9
+ development: "http://localhost:3000/backend/services/v0.0.1"
10
+ };
11
+ var WallyApiClient = class _WallyApiClient {
12
+ config;
13
+ constructor(config) {
14
+ this.config = config;
15
+ }
16
+ /**
17
+ * Resolve a configuração, extraindo organizationId da API Key
18
+ */
19
+ static async resolveConfig(config) {
20
+ const apiUrl = config.apiUrl || ENVIRONMENTS[config.environment || "production"];
21
+ const orgId = await _WallyApiClient.validateApiKey(apiUrl, config.apiKey);
22
+ return {
23
+ apiKey: config.apiKey,
24
+ projectId: config.projectId,
25
+ normId: config.normId,
26
+ qualityGateId: config.qualityGateId,
27
+ environment: config.environment || "production",
28
+ apiUrl,
29
+ timeout: config.timeout ?? 3e4,
30
+ failOnError: config.failOnError ?? false,
31
+ organizationId: orgId
32
+ };
33
+ }
34
+ /**
35
+ * Valida a API Key e retorna o organizationId
36
+ */
37
+ static async validateApiKey(apiUrl, apiKey) {
38
+ const response = await fetch(`${apiUrl}/_/auth/api-key/info`, {
39
+ method: "GET",
40
+ headers: {
41
+ "x-api-key": apiKey,
42
+ "Content-Type": "application/json"
43
+ }
44
+ });
45
+ if (!response.ok) {
46
+ const errorText = await response.text();
47
+ throw new Error(`Falha ao validar API Key: ${response.status} - ${errorText}`);
48
+ }
49
+ const data = await response.json();
50
+ if (!data.organizationId) {
51
+ throw new Error("API Key inv\xE1lida: organizationId n\xE3o encontrado na resposta");
52
+ }
53
+ return data.organizationId;
54
+ }
55
+ /**
56
+ * Cria uma nova sessão de navegação
57
+ */
58
+ async createSession(options = {}) {
59
+ const endpoint = `${this.config.apiUrl}/${this.config.organizationId}/sessions`;
60
+ const body = {
61
+ projectId: this.config.projectId,
62
+ normId: this.config.normId,
63
+ qualityGateId: this.config.qualityGateId,
64
+ source: "cypress",
65
+ name: options.name || `Cypress Session - ${(/* @__PURE__ */ new Date()).toLocaleString("pt-BR")}`,
66
+ description: options.description,
67
+ baseUrl: options.baseUrl,
68
+ metadata: {
69
+ cypressVersion: typeof Cypress !== "undefined" ? Cypress.version : "unknown",
70
+ nodeVersion: process.version,
71
+ platform: process.platform
72
+ }
73
+ };
74
+ const response = await this.request("POST", endpoint, body);
75
+ return response;
76
+ }
77
+ /**
78
+ * Adiciona uma página à sessão usando upload binário comprimido (gzip)
79
+ */
80
+ async addPageBinary(sessionId, pageData) {
81
+ const endpoint = `${this.config.apiUrl}/${this.config.organizationId}/sessions/${sessionId}/pages/binary`;
82
+ const htmlBuffer = Buffer.from(pageData.html, "utf-8");
83
+ const compressedBuffer = zlib.gzipSync(htmlBuffer);
84
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
85
+ const response = await fetch(endpoint, {
86
+ method: "POST",
87
+ headers: {
88
+ "x-api-key": this.config.apiKey,
89
+ "Content-Type": "application/gzip",
90
+ "X-Page-Url": pageData.url,
91
+ "X-Page-Title": encodeURIComponent(pageData.title),
92
+ "X-Page-Timestamp": timestamp
93
+ },
94
+ body: compressedBuffer,
95
+ signal: AbortSignal.timeout(this.config.timeout)
96
+ });
97
+ if (!response.ok) {
98
+ const errorText = await response.text();
99
+ throw new Error(`Erro ao enviar p\xE1gina: ${response.status} - ${errorText}`);
100
+ }
101
+ return response.json();
102
+ }
103
+ /**
104
+ * Finaliza a sessão e opcionalmente inicia análise
105
+ */
106
+ async finishSession(sessionId) {
107
+ const endpoint = `${this.config.apiUrl}/${this.config.organizationId}/sessions/${sessionId}/finish`;
108
+ return this.request("POST", endpoint, {
109
+ startAnalysis: true
110
+ });
111
+ }
112
+ /**
113
+ * Obtém detalhes de uma sessão
114
+ */
115
+ async getSession(sessionId) {
116
+ const endpoint = `${this.config.apiUrl}/${this.config.organizationId}/sessions/${sessionId}`;
117
+ return this.request("GET", endpoint);
118
+ }
119
+ /**
120
+ * Executa uma requisição HTTP genérica
121
+ */
122
+ async request(method, url, body) {
123
+ const options = {
124
+ method,
125
+ headers: {
126
+ "x-api-key": this.config.apiKey,
127
+ "Content-Type": "application/json"
128
+ },
129
+ signal: AbortSignal.timeout(this.config.timeout)
130
+ };
131
+ if (body && method !== "GET") {
132
+ options.body = JSON.stringify(body);
133
+ }
134
+ const response = await fetch(url, options);
135
+ if (!response.ok) {
136
+ const errorText = await response.text();
137
+ let errorMessage;
138
+ try {
139
+ const errorData = JSON.parse(errorText);
140
+ errorMessage = errorData.message || errorData.error || `Erro ${response.status}`;
141
+ } catch {
142
+ errorMessage = `Erro ${response.status}: ${response.statusText}`;
143
+ }
144
+ throw new Error(errorMessage);
145
+ }
146
+ return response.json();
147
+ }
148
+ };
149
+
150
+ // src/plugin.ts
151
+ var state = {
152
+ config: null,
153
+ session: null,
154
+ initialized: false,
155
+ lastError: null
156
+ };
157
+ var apiClient = null;
158
+ function log(message, ...args) {
159
+ console.log(`[Wally] ${message}`, ...args);
160
+ }
161
+ function logError(message, error) {
162
+ console.error(`[Wally] \u274C ${message}`, error?.message || "");
163
+ state.lastError = error?.message || message;
164
+ }
165
+ async function initialize(config) {
166
+ try {
167
+ log("\u{1F680} Inicializando plugin...");
168
+ if (!config.apiKey) {
169
+ throw new Error("apiKey \xE9 obrigat\xF3rio");
170
+ }
171
+ if (!config.projectId) {
172
+ throw new Error("projectId \xE9 obrigat\xF3rio");
173
+ }
174
+ const resolvedConfig = await WallyApiClient.resolveConfig(config);
175
+ state.config = resolvedConfig;
176
+ apiClient = new WallyApiClient(resolvedConfig);
177
+ state.initialized = true;
178
+ log("\u2705 Plugin inicializado", {
179
+ organizationId: resolvedConfig.organizationId,
180
+ projectId: resolvedConfig.projectId,
181
+ environment: resolvedConfig.environment
182
+ });
183
+ return true;
184
+ } catch (error) {
185
+ logError("Falha ao inicializar plugin", error);
186
+ state.initialized = false;
187
+ return false;
188
+ }
189
+ }
190
+ async function startSession(baseUrl) {
191
+ if (!state.initialized || !apiClient) {
192
+ logError("Plugin n\xE3o inicializado");
193
+ return null;
194
+ }
195
+ if (state.session) {
196
+ log("\u26A0\uFE0F Sess\xE3o j\xE1 ativa, reutilizando:", state.session.id);
197
+ return state.session;
198
+ }
199
+ try {
200
+ log("\u{1F3AC} Criando sess\xE3o de navega\xE7\xE3o...");
201
+ const response = await apiClient.createSession({
202
+ name: `Cypress - ${(/* @__PURE__ */ new Date()).toLocaleString("pt-BR")}`,
203
+ description: "Sess\xE3o autom\xE1tica via wally-cypress",
204
+ baseUrl
205
+ });
206
+ state.session = {
207
+ id: response.id,
208
+ organizationId: state.config?.organizationId || "",
209
+ status: "ACTIVE",
210
+ startedAt: /* @__PURE__ */ new Date(),
211
+ pages: []
212
+ };
213
+ log("\u2705 Sess\xE3o criada:", state.session.id);
214
+ return state.session;
215
+ } catch (error) {
216
+ logError("Falha ao criar sess\xE3o", error);
217
+ return null;
218
+ }
219
+ }
220
+ async function capturePage(pageName, url, html) {
221
+ if (!state.initialized || !apiClient || !state.session) {
222
+ logError("Plugin n\xE3o inicializado ou sess\xE3o n\xE3o ativa");
223
+ return null;
224
+ }
225
+ try {
226
+ log(`\u{1F4C4} Capturando p\xE1gina: "${pageName}" (${url})`);
227
+ const response = await apiClient.addPageBinary(state.session.id, {
228
+ url,
229
+ title: pageName,
230
+ html
231
+ });
232
+ const page = {
233
+ id: response.pageId,
234
+ url,
235
+ title: pageName,
236
+ timestamp: /* @__PURE__ */ new Date(),
237
+ contentSize: response.totalSize
238
+ };
239
+ state.session.pages.push(page);
240
+ log(`\u2705 P\xE1gina capturada: ${pageName} (${response.totalPages} total)`);
241
+ return page;
242
+ } catch (error) {
243
+ logError(`Falha ao capturar p\xE1gina: ${pageName}`, error);
244
+ return null;
245
+ }
246
+ }
247
+ async function finishSession() {
248
+ if (!state.initialized || !apiClient) {
249
+ logError("Plugin n\xE3o inicializado");
250
+ return null;
251
+ }
252
+ if (!state.session) {
253
+ log("\u26A0\uFE0F Nenhuma sess\xE3o ativa para finalizar");
254
+ return null;
255
+ }
256
+ try {
257
+ log("\u{1F3C1} Finalizando sess\xE3o...");
258
+ const response = await apiClient.finishSession(state.session.id);
259
+ log("\u2705 Sess\xE3o finalizada:", {
260
+ sessionId: response.sessionId,
261
+ totalPages: response.totalPages,
262
+ analysisId: response.analysisId
263
+ });
264
+ const result = response;
265
+ state.session = null;
266
+ return result;
267
+ } catch (error) {
268
+ logError("Falha ao finalizar sess\xE3o", error);
269
+ return null;
270
+ }
271
+ }
272
+ function getSession() {
273
+ return state.session;
274
+ }
275
+ function getConfig() {
276
+ return state.config;
277
+ }
278
+ function wallyPlugin(on, config, wallyConfig) {
279
+ on("task", {
280
+ /**
281
+ * Inicializa o plugin (chamado automaticamente no before:run)
282
+ */
283
+ "wally:initialize": async () => {
284
+ const success = await initialize(wallyConfig);
285
+ return success;
286
+ },
287
+ /**
288
+ * Inicia uma nova sessão de navegação
289
+ */
290
+ "wally:startSession": async (baseUrl) => {
291
+ const session = await startSession(baseUrl);
292
+ return session;
293
+ },
294
+ /**
295
+ * Captura uma página
296
+ */
297
+ "wally:capturePage": async (args) => {
298
+ const page = await capturePage(args.pageName, args.url, args.html);
299
+ return page;
300
+ },
301
+ /**
302
+ * Finaliza a sessão
303
+ */
304
+ "wally:finishSession": async () => {
305
+ const result = await finishSession();
306
+ return result;
307
+ },
308
+ /**
309
+ * Obtém a sessão atual
310
+ */
311
+ "wally:getSession": () => {
312
+ return getSession();
313
+ },
314
+ /**
315
+ * Obtém configuração atual
316
+ */
317
+ "wally:getConfig": () => {
318
+ return getConfig();
319
+ },
320
+ /**
321
+ * Obtém último erro
322
+ */
323
+ "wally:getLastError": () => {
324
+ return state.lastError;
325
+ }
326
+ });
327
+ on("before:run", async () => {
328
+ log("\u{1F4E6} Hook before:run - Inicializando Wally...");
329
+ await initialize(wallyConfig);
330
+ });
331
+ on("after:run", async () => {
332
+ log("\u{1F4E6} Hook after:run - Finalizando Wally...");
333
+ if (state.session) {
334
+ await finishSession();
335
+ }
336
+ });
337
+ on("before:spec", async (spec) => {
338
+ log(`\u{1F4CB} Hook before:spec - ${spec.relative}`);
339
+ if (!state.initialized) {
340
+ await initialize(wallyConfig);
341
+ }
342
+ if (!state.session) {
343
+ await startSession(config.baseUrl || void 0);
344
+ }
345
+ });
346
+ on("after:spec", async (spec, results) => {
347
+ log(`\u{1F4CB} Hook after:spec - ${spec.relative}`, {
348
+ tests: results.stats.tests,
349
+ passed: results.stats.passes,
350
+ failed: results.stats.failures
351
+ });
352
+ });
353
+ }
354
+
355
+ exports.capturePage = capturePage;
356
+ exports.finishSession = finishSession;
357
+ exports.getConfig = getConfig;
358
+ exports.getSession = getSession;
359
+ exports.initialize = initialize;
360
+ exports.startSession = startSession;
361
+ exports.wallyPlugin = wallyPlugin;
362
+ //# sourceMappingURL=plugin.js.map
363
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/api-client.ts","../src/plugin.ts"],"names":["gzipSync"],"mappings":"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","file":"plugin.js","sourcesContent":["/**\n * API Client para comunicação com o backend Wally\n *\n * Segue o mesmo padrão do wally-chrome (SessionApi)\n */\n\nimport { gzipSync } from 'zlib';\nimport type {\n AddPageResponse,\n CreateSessionResponse,\n FinishSessionResponse,\n ResolvedConfig,\n WallyConfig,\n WallySession,\n} from './types';\n\n/**\n * URLs base por ambiente\n */\nconst ENVIRONMENTS = {\n production: 'https://api.wally.equallyze.com/backend/services/v0.0.1',\n staging: 'https://api.stg.wally.equallyze.com/backend/services/v0.0.1',\n development: 'http://localhost:3000/backend/services/v0.0.1',\n} as const;\n\n/**\n * Cliente HTTP para API Wally\n */\nexport class WallyApiClient {\n private config: ResolvedConfig;\n\n constructor(config: ResolvedConfig) {\n this.config = config;\n }\n\n /**\n * Resolve a configuração, extraindo organizationId da API Key\n */\n static async resolveConfig(config: WallyConfig): Promise<ResolvedConfig> {\n // Determina URL base\n const apiUrl = config.apiUrl || ENVIRONMENTS[config.environment || 'production'];\n\n // Valida API Key e extrai organizationId\n const orgId = await WallyApiClient.validateApiKey(apiUrl, config.apiKey);\n\n return {\n apiKey: config.apiKey,\n projectId: config.projectId,\n normId: config.normId,\n qualityGateId: config.qualityGateId,\n environment: config.environment || 'production',\n apiUrl,\n timeout: config.timeout ?? 30000,\n failOnError: config.failOnError ?? false,\n organizationId: orgId,\n };\n }\n\n /**\n * Valida a API Key e retorna o organizationId\n */\n private static async validateApiKey(apiUrl: string, apiKey: string): Promise<string> {\n const response = await fetch(`${apiUrl}/_/auth/api-key/info`, {\n method: 'GET',\n headers: {\n 'x-api-key': apiKey,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Falha ao validar API Key: ${response.status} - ${errorText}`);\n }\n\n const data = (await response.json()) as { organizationId?: string };\n\n if (!data.organizationId) {\n throw new Error('API Key inválida: organizationId não encontrado na resposta');\n }\n\n return data.organizationId;\n }\n\n /**\n * Cria uma nova sessão de navegação\n */\n async createSession(\n options: {\n name?: string;\n description?: string;\n baseUrl?: string;\n } = {},\n ): Promise<CreateSessionResponse> {\n const endpoint = `${this.config.apiUrl}/${this.config.organizationId}/sessions`;\n\n const body = {\n projectId: this.config.projectId,\n normId: this.config.normId,\n qualityGateId: this.config.qualityGateId,\n source: 'cypress',\n name: options.name || `Cypress Session - ${new Date().toLocaleString('pt-BR')}`,\n description: options.description,\n baseUrl: options.baseUrl,\n metadata: {\n cypressVersion: typeof Cypress !== 'undefined' ? Cypress.version : 'unknown',\n nodeVersion: process.version,\n platform: process.platform,\n },\n };\n\n const response = await this.request<CreateSessionResponse>('POST', endpoint, body);\n return response;\n }\n\n /**\n * Adiciona uma página à sessão usando upload binário comprimido (gzip)\n */\n async addPageBinary(\n sessionId: string,\n pageData: { url: string; title: string; html: string },\n ): Promise<AddPageResponse> {\n const endpoint = `${this.config.apiUrl}/${this.config.organizationId}/sessions/${sessionId}/pages/binary`;\n\n // Comprime o HTML com gzip\n const htmlBuffer = Buffer.from(pageData.html, 'utf-8');\n const compressedBuffer = gzipSync(htmlBuffer);\n\n const timestamp = new Date().toISOString();\n\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: {\n 'x-api-key': this.config.apiKey,\n 'Content-Type': 'application/gzip',\n 'X-Page-Url': pageData.url,\n 'X-Page-Title': encodeURIComponent(pageData.title),\n 'X-Page-Timestamp': timestamp,\n },\n body: compressedBuffer,\n signal: AbortSignal.timeout(this.config.timeout),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Erro ao enviar página: ${response.status} - ${errorText}`);\n }\n\n return response.json() as Promise<AddPageResponse>;\n }\n\n /**\n * Finaliza a sessão e opcionalmente inicia análise\n */\n async finishSession(sessionId: string): Promise<FinishSessionResponse> {\n const endpoint = `${this.config.apiUrl}/${this.config.organizationId}/sessions/${sessionId}/finish`;\n\n return this.request<FinishSessionResponse>('POST', endpoint, {\n startAnalysis: true,\n });\n }\n\n /**\n * Obtém detalhes de uma sessão\n */\n async getSession(sessionId: string): Promise<WallySession> {\n const endpoint = `${this.config.apiUrl}/${this.config.organizationId}/sessions/${sessionId}`;\n return this.request<WallySession>('GET', endpoint);\n }\n\n /**\n * Executa uma requisição HTTP genérica\n */\n private async request<T>(\n method: 'GET' | 'POST' | 'DELETE',\n url: string,\n body?: unknown,\n ): Promise<T> {\n const options: RequestInit = {\n method,\n headers: {\n 'x-api-key': this.config.apiKey,\n 'Content-Type': 'application/json',\n },\n signal: AbortSignal.timeout(this.config.timeout),\n };\n\n if (body && method !== 'GET') {\n options.body = JSON.stringify(body);\n }\n\n const response = await fetch(url, options);\n\n if (!response.ok) {\n const errorText = await response.text();\n let errorMessage: string;\n\n try {\n const errorData = JSON.parse(errorText);\n errorMessage = errorData.message || errorData.error || `Erro ${response.status}`;\n } catch {\n errorMessage = `Erro ${response.status}: ${response.statusText}`;\n }\n\n throw new Error(errorMessage);\n }\n\n return response.json() as Promise<T>;\n }\n}\n","/**\n * Plugin Cypress para integração com Wally\n *\n * Este módulo é executado no Node.js (setupNodeEvents) e gerencia:\n * - Sessão de navegação\n * - Comunicação com API Wally\n * - Ciclo de vida dos testes\n *\n * @example\n * // cypress.config.ts\n * import { wallyPlugin } from '@equallyze/wally-cypress/plugin';\n *\n * export default defineConfig({\n * e2e: {\n * setupNodeEvents(on, config) {\n * wallyPlugin(on, config, {\n * apiKey: process.env.WALLY_API_KEY,\n * projectId: process.env.WALLY_PROJECT_ID,\n * });\n * return config;\n * },\n * },\n * });\n */\n\nimport { WallyApiClient } from './api-client';\nimport type {\n FinishSessionResponse,\n ResolvedConfig,\n WallyConfig,\n WallyPage,\n WallyPluginState,\n WallySession,\n} from './types';\n\n/**\n * Estado global do plugin\n */\nconst state: WallyPluginState = {\n config: null,\n session: null,\n initialized: false,\n lastError: null,\n};\n\n/**\n * Cliente API (inicializado no before:run)\n */\nlet apiClient: WallyApiClient | null = null;\n\n/**\n * Logger\n */\nfunction log(message: string, ...args: unknown[]) {\n console.log(`[Wally] ${message}`, ...args);\n}\n\nfunction logError(message: string, error?: Error) {\n console.error(`[Wally] ❌ ${message}`, error?.message || '');\n state.lastError = error?.message || message;\n}\n\n/**\n * Inicializa o plugin Wally\n */\nasync function initialize(config: WallyConfig): Promise<boolean> {\n try {\n log('🚀 Inicializando plugin...');\n\n // Valida configuração obrigatória\n if (!config.apiKey) {\n throw new Error('apiKey é obrigatório');\n }\n if (!config.projectId) {\n throw new Error('projectId é obrigatório');\n }\n\n // Resolve configuração (valida API Key e obtém organizationId)\n const resolvedConfig = await WallyApiClient.resolveConfig(config);\n state.config = resolvedConfig;\n\n // Cria cliente API\n apiClient = new WallyApiClient(resolvedConfig);\n\n state.initialized = true;\n log('✅ Plugin inicializado', {\n organizationId: resolvedConfig.organizationId,\n projectId: resolvedConfig.projectId,\n environment: resolvedConfig.environment,\n });\n\n return true;\n } catch (error) {\n logError('Falha ao inicializar plugin', error as Error);\n state.initialized = false;\n return false;\n }\n}\n\n/**\n * Inicia uma nova sessão de navegação\n */\nasync function startSession(baseUrl?: string): Promise<WallySession | null> {\n if (!state.initialized || !apiClient) {\n logError('Plugin não inicializado');\n return null;\n }\n\n if (state.session) {\n log('⚠️ Sessão já ativa, reutilizando:', state.session.id);\n return state.session;\n }\n\n try {\n log('🎬 Criando sessão de navegação...');\n\n const response = await apiClient.createSession({\n name: `Cypress - ${new Date().toLocaleString('pt-BR')}`,\n description: 'Sessão automática via wally-cypress',\n baseUrl,\n });\n\n state.session = {\n id: response.id,\n organizationId: state.config?.organizationId || '',\n status: 'ACTIVE',\n startedAt: new Date(),\n pages: [],\n };\n\n log('✅ Sessão criada:', state.session.id);\n return state.session;\n } catch (error) {\n logError('Falha ao criar sessão', error as Error);\n return null;\n }\n}\n\n/**\n * Captura uma página e envia para o backend\n */\nasync function capturePage(pageName: string, url: string, html: string): Promise<WallyPage | null> {\n if (!state.initialized || !apiClient || !state.session) {\n logError('Plugin não inicializado ou sessão não ativa');\n return null;\n }\n\n try {\n log(`📄 Capturando página: \"${pageName}\" (${url})`);\n\n const response = await apiClient.addPageBinary(state.session.id, {\n url,\n title: pageName,\n html,\n });\n\n const page: WallyPage = {\n id: response.pageId,\n url,\n title: pageName,\n timestamp: new Date(),\n contentSize: response.totalSize,\n };\n\n state.session.pages.push(page);\n\n log(`✅ Página capturada: ${pageName} (${response.totalPages} total)`);\n return page;\n } catch (error) {\n logError(`Falha ao capturar página: ${pageName}`, error as Error);\n return null;\n }\n}\n\n/**\n * Finaliza a sessão e inicia análise\n */\nasync function finishSession(): Promise<FinishSessionResponse | null> {\n if (!state.initialized || !apiClient) {\n logError('Plugin não inicializado');\n return null;\n }\n\n if (!state.session) {\n log('⚠️ Nenhuma sessão ativa para finalizar');\n return null;\n }\n\n try {\n log('🏁 Finalizando sessão...');\n\n const response = await apiClient.finishSession(state.session.id);\n\n log('✅ Sessão finalizada:', {\n sessionId: response.sessionId,\n totalPages: response.totalPages,\n analysisId: response.analysisId,\n });\n\n // Limpa estado\n const result = response;\n state.session = null;\n\n return result;\n } catch (error) {\n logError('Falha ao finalizar sessão', error as Error);\n return null;\n }\n}\n\n/**\n * Obtém o estado atual da sessão\n */\nfunction getSession(): WallySession | null {\n return state.session;\n}\n\n/**\n * Obtém configuração atual\n */\nfunction getConfig(): ResolvedConfig | null {\n return state.config;\n}\n\n/**\n * Plugin principal para Cypress\n *\n * @param on - Objeto de eventos Cypress\n * @param config - Configuração Cypress\n * @param wallyConfig - Configuração do plugin Wally\n */\nexport function wallyPlugin(\n on: Cypress.PluginEvents,\n config: Cypress.PluginConfigOptions,\n wallyConfig: WallyConfig,\n): void {\n // Registra tasks para comunicação com os comandos\n on('task', {\n /**\n * Inicializa o plugin (chamado automaticamente no before:run)\n */\n 'wally:initialize': async () => {\n const success = await initialize(wallyConfig);\n return success;\n },\n\n /**\n * Inicia uma nova sessão de navegação\n */\n 'wally:startSession': async (baseUrl?: string) => {\n const session = await startSession(baseUrl);\n return session;\n },\n\n /**\n * Captura uma página\n */\n 'wally:capturePage': async (args: { pageName: string; url: string; html: string }) => {\n const page = await capturePage(args.pageName, args.url, args.html);\n return page;\n },\n\n /**\n * Finaliza a sessão\n */\n 'wally:finishSession': async () => {\n const result = await finishSession();\n return result;\n },\n\n /**\n * Obtém a sessão atual\n */\n 'wally:getSession': () => {\n return getSession();\n },\n\n /**\n * Obtém configuração atual\n */\n 'wally:getConfig': () => {\n return getConfig();\n },\n\n /**\n * Obtém último erro\n */\n 'wally:getLastError': () => {\n return state.lastError;\n },\n });\n\n // Hook: antes de iniciar os testes\n on('before:run', async () => {\n log('📦 Hook before:run - Inicializando Wally...');\n await initialize(wallyConfig);\n });\n\n // Hook: após finalizar os testes\n on('after:run', async () => {\n log('📦 Hook after:run - Finalizando Wally...');\n if (state.session) {\n await finishSession();\n }\n });\n\n // Hook: antes de cada spec\n on('before:spec', async (spec) => {\n log(`📋 Hook before:spec - ${spec.relative}`);\n\n // Garante que plugin está inicializado\n if (!state.initialized) {\n await initialize(wallyConfig);\n }\n\n // Inicia sessão para esta spec (se não houver sessão ativa)\n if (!state.session) {\n await startSession(config.baseUrl || undefined);\n }\n });\n\n // Hook: após cada spec (opcional: pode manter sessão entre specs)\n on('after:spec', async (spec, results) => {\n log(`📋 Hook after:spec - ${spec.relative}`, {\n tests: results.stats.tests,\n passed: results.stats.passes,\n failed: results.stats.failures,\n });\n\n // Nota: Não finalizamos aqui para permitir múltiplas specs na mesma sessão\n // A sessão será finalizada no after:run\n });\n}\n\n// Export adicional para uso direto\nexport { capturePage, finishSession, getConfig, getSession, initialize, startSession };\n"]}