Skip to content

Commit cf74f86

Browse files
errors + devicename
1 parent e992c9b commit cf74f86

File tree

11 files changed

+610
-85
lines changed

11 files changed

+610
-85
lines changed

src/providers/base_provider.ts

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import utils from '../utils';
66
import Upload from '../upload';
77
import platform from '../utils/platform';
88
import logger from '../logger';
9+
import { handleAxiosError, isNetworkError } from '../utils/error-helpers';
10+
import {
11+
checkInternetConnectivity,
12+
formatConnectivityResults,
13+
} from '../utils/connectivity';
914

1015
/**
1116
* Common interface for run information shared by all providers
@@ -208,7 +213,8 @@ export default abstract class BaseProvider<
208213
}
209214

210215
/**
211-
* Extracts an error message from various error types
216+
* Extracts an error message from various error types.
217+
* For Axios errors, uses enhanced error handling with diagnostics.
212218
*/
213219
protected extractErrorMessage(cause: unknown): string | null {
214220
if (typeof cause === 'string') {
@@ -220,7 +226,14 @@ export default abstract class BaseProvider<
220226
}
221227

222228
if (cause && typeof cause === 'object') {
223-
const axiosError = cause as {
229+
// Use enhanced error handling for real Axios errors
230+
if (axios.isAxiosError(cause)) {
231+
const enhanced = handleAxiosError(cause, 'Request failed');
232+
return enhanced.message;
233+
}
234+
235+
// Handle error-like objects with response property (for backwards compatibility)
236+
const axiosLikeError = cause as {
224237
response?: {
225238
status?: number;
226239
data?: { error?: string; errors?: string[]; message?: string };
@@ -229,18 +242,19 @@ export default abstract class BaseProvider<
229242
};
230243

231244
// Check for 429 status code (credits depleted)
232-
if (axiosError.response?.status === 429) {
245+
if (axiosLikeError.response?.status === 429) {
233246
return 'Your TestingBot credits are depleted. Please upgrade your plan at https://testingbot.com/pricing';
234247
}
235248

236-
if (axiosError.response?.data?.errors) {
237-
return axiosError.response.data.errors.join('\n');
249+
// Extract error message from response data
250+
if (axiosLikeError.response?.data?.errors) {
251+
return axiosLikeError.response.data.errors.join('\n');
238252
}
239-
if (axiosError.response?.data?.error) {
240-
return axiosError.response.data.error;
253+
if (axiosLikeError.response?.data?.error) {
254+
return axiosLikeError.response.data.error;
241255
}
242-
if (axiosError.response?.data?.message) {
243-
return axiosError.response.data.message;
256+
if (axiosLikeError.response?.data?.message) {
257+
return axiosLikeError.response.data.message;
244258
}
245259

246260
if (cause instanceof Error) {
@@ -266,6 +280,58 @@ export default abstract class BaseProvider<
266280
return null;
267281
}
268282

283+
/**
284+
* Checks internet connectivity and logs diagnostic information.
285+
* Useful when network errors occur to help users troubleshoot.
286+
*/
287+
protected async checkAndReportConnectivity(): Promise<boolean> {
288+
logger.info('Checking internet connectivity...');
289+
const result = await checkInternetConnectivity();
290+
logger.info(formatConnectivityResults(result));
291+
return result.connected;
292+
}
293+
294+
/**
295+
* Performs a quick connectivity check before starting operations.
296+
* Throws an error with diagnostics if no connection is available.
297+
*/
298+
protected async ensureConnectivity(): Promise<void> {
299+
const result = await checkInternetConnectivity();
300+
if (!result.connected) {
301+
logger.error('No internet connection detected.');
302+
logger.error(formatConnectivityResults(result));
303+
throw new TestingBotError(
304+
'No internet connection. Please check your network and try again.',
305+
);
306+
}
307+
}
308+
309+
/**
310+
* Handles errors with enhanced diagnostics.
311+
* For network errors, performs connectivity check.
312+
*/
313+
protected async handleErrorWithDiagnostics(
314+
error: unknown,
315+
operation: string,
316+
): Promise<TestingBotError> {
317+
if (axios.isAxiosError(error)) {
318+
// For network errors, check connectivity
319+
if (isNetworkError(error)) {
320+
await this.checkAndReportConnectivity();
321+
}
322+
return handleAxiosError(error, operation);
323+
}
324+
325+
if (error instanceof TestingBotError) {
326+
return error;
327+
}
328+
329+
return new TestingBotError(
330+
`${operation}: ${error instanceof Error ? error.message : 'Unknown error'}`,
331+
{ cause: error instanceof Error ? error : undefined },
332+
);
333+
}
334+
269335
/**
270336
* Formats elapsed time in human-readable format
271337
*/

src/providers/espresso.ts

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ import TestingBotError from '../models/testingbot_error';
99
import utils from '../utils';
1010
import BaseProvider from './base_provider';
1111

12+
export interface EspressoRunEnvironment {
13+
device?: string;
14+
name?: string;
15+
version?: string;
16+
}
17+
1218
export interface EspressoRunInfo {
1319
id: number;
1420
status: 'WAITING' | 'READY' | 'DONE' | 'FAILED';
@@ -17,6 +23,7 @@ export interface EspressoRunInfo {
1723
platformName: string;
1824
version?: string;
1925
};
26+
environment?: EspressoRunEnvironment;
2027
success: number;
2128
report?: string;
2229
}
@@ -94,6 +101,9 @@ export default class Espresso extends BaseProvider<EspressoOptions> {
94101
}
95102

96103
try {
104+
// Quick connectivity check before starting uploads
105+
await this.ensureConnectivity();
106+
97107
if (!this.options.quiet) {
98108
logger.info('Uploading Espresso App');
99109
}
@@ -195,6 +205,7 @@ export default class Espresso extends BaseProvider<EspressoOptions> {
195205
username: this.credentials.userName,
196206
password: this.credentials.accessKey,
197207
},
208+
timeout: 30000, // 30 second timeout
198209
},
199210
);
200211

@@ -223,9 +234,10 @@ export default class Espresso extends BaseProvider<EspressoOptions> {
223234
if (error instanceof TestingBotError) {
224235
throw error;
225236
}
226-
throw new TestingBotError(`Running Espresso test failed`, {
227-
cause: error,
228-
});
237+
throw await this.handleErrorWithDiagnostics(
238+
error,
239+
'Running Espresso test failed',
240+
);
229241
}
230242
}
231243

@@ -239,6 +251,7 @@ export default class Espresso extends BaseProvider<EspressoOptions> {
239251
username: this.credentials.userName,
240252
password: this.credentials.accessKey,
241253
},
254+
timeout: 30000, // 30 second timeout
242255
});
243256

244257
// Check for version update notification
@@ -247,9 +260,10 @@ export default class Espresso extends BaseProvider<EspressoOptions> {
247260

248261
return response.data;
249262
} catch (error) {
250-
throw new TestingBotError(`Failed to get Espresso test status`, {
251-
cause: error,
252-
});
263+
throw await this.handleErrorWithDiagnostics(
264+
error,
265+
'Failed to get Espresso test status',
266+
);
253267
}
254268
}
255269

@@ -284,7 +298,7 @@ export default class Espresso extends BaseProvider<EspressoOptions> {
284298
const statusText =
285299
run.success === 1 ? 'Test completed successfully' : 'Test failed';
286300
console.log(
287-
` ${statusEmoji} Run ${run.id} (${run.capabilities.deviceName}): ${statusText}`,
301+
` ${statusEmoji} Run ${run.id} (${this.getRunDisplayName(run)}): ${statusText}`,
288302
);
289303
}
290304
}
@@ -300,7 +314,7 @@ export default class Espresso extends BaseProvider<EspressoOptions> {
300314
logger.error(`${failedRuns.length} test run(s) failed:`);
301315
for (const run of failedRuns) {
302316
logger.error(
303-
` - Run ${run.id} (${run.capabilities.deviceName}): ${run.report || 'No report available'}`,
317+
` - Run ${run.id} (${this.getRunDisplayName(run)}): ${run.report || 'No report available'}`,
304318
);
305319
}
306320
}
@@ -350,16 +364,24 @@ export default class Espresso extends BaseProvider<EspressoOptions> {
350364
const statusInfo = this.getStatusInfo(run.status);
351365

352366
if (run.status === 'WAITING' || run.status === 'READY') {
353-
const message = ` ${statusInfo.emoji} Run ${run.id} (${run.capabilities.deviceName}): ${statusInfo.text} (${elapsedStr})`;
367+
const message = ` ${statusInfo.emoji} Run ${run.id} (${this.getRunDisplayName(run)}): ${statusInfo.text} (${elapsedStr})`;
354368
process.stdout.write(`\r${message}`);
355369
} else if (statusChanged) {
356370
console.log(
357-
` ${statusInfo.emoji} Run ${run.id} (${run.capabilities.deviceName}): ${statusInfo.text}`,
371+
` ${statusInfo.emoji} Run ${run.id} (${this.getRunDisplayName(run)}): ${statusInfo.text}`,
358372
);
359373
}
360374
}
361375
}
362376

377+
/**
378+
* Get the display name for a run, preferring environment.name over capabilities.deviceName
379+
* This shows the actual device used when a wildcard (*) was specified
380+
*/
381+
private getRunDisplayName(run: EspressoRunInfo): string {
382+
return run.environment?.name || run.capabilities.deviceName;
383+
}
384+
363385
private getStatusInfo(status: EspressoRunInfo['status']): {
364386
emoji: string;
365387
text: string;
@@ -405,6 +427,7 @@ export default class Espresso extends BaseProvider<EspressoOptions> {
405427
username: this.credentials.userName,
406428
password: this.credentials.accessKey,
407429
},
430+
timeout: 30000, // 30 second timeout
408431
});
409432

410433
// Check for version update notification

0 commit comments

Comments
 (0)