Skip to content

Commit 5f673d7

Browse files
show warning when running maestro flows targetting a single physical device
1 parent 30bd4c5 commit 5f673d7

3 files changed

Lines changed: 212 additions & 0 deletions

File tree

src/providers/maestro.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,20 @@ export default class Maestro extends BaseProvider<MaestroOptions> {
411411

412412
if (!this.options.quiet) {
413413
this.logIncludedFiles(allFlowFiles, baseDir);
414+
415+
// Show info about potential slow execution on specific real devices
416+
utils.showRealDeviceFlowsInfo({
417+
realDevice: this.options.realDevice,
418+
device: this.options.device,
419+
version: this.options.version,
420+
flowCount: allFlowFiles.filter(
421+
(f) =>
422+
f.endsWith('.yaml') || f.endsWith('.yml'),
423+
).filter(
424+
(f) => !f.endsWith('config.yaml'),
425+
).length,
426+
shardSplit: this.options.shardSplit,
427+
});
414428
}
415429

416430
// Check for missing file references and warn the user

src/utils.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import logger from './logger';
33
import pc from 'picocolors';
44

55
let versionCheckDisplayed = false;
6+
let realDeviceFlowsInfoDisplayed = false;
67

78
export default {
89
getUserAgent(): string {
@@ -30,6 +31,88 @@ export default {
3031
return 0;
3132
},
3233

34+
/**
35+
* Check if a device specification is a wildcard or regex pattern
36+
*/
37+
isWildcardDevice(device: string | undefined): boolean {
38+
if (!device) return true;
39+
// Check for common wildcard/regex characters
40+
return device === '*' || device.includes('*') || device.includes('?') || device.includes('.*');
41+
},
42+
43+
/**
44+
* Check if a version specification is a wildcard or regex pattern
45+
*/
46+
isWildcardVersion(version: string | undefined): boolean {
47+
if (!version) return true;
48+
// Check for common wildcard/regex characters
49+
return version === '*' || version.includes('*') || version.includes('?') || version.includes('.*');
50+
},
51+
52+
/**
53+
* Show info message when running many flows on a specific real device without sharding
54+
*/
55+
showRealDeviceFlowsInfo(options: {
56+
realDevice: boolean;
57+
device?: string;
58+
version?: string;
59+
flowCount: number;
60+
shardSplit?: number;
61+
}): void {
62+
// Only show once
63+
if (realDeviceFlowsInfoDisplayed) {
64+
return;
65+
}
66+
67+
// Check conditions: real device, specific device, more than 2 flows, no shards
68+
if (
69+
!options.realDevice ||
70+
this.isWildcardDevice(options.device) ||
71+
options.flowCount <= 2 ||
72+
options.shardSplit
73+
) {
74+
return;
75+
}
76+
77+
realDeviceFlowsInfoDisplayed = true;
78+
const border = '─'.repeat(80);
79+
80+
logger.info('');
81+
logger.info(pc.cyan(border));
82+
logger.info(pc.cyan('ℹ Performance Tip'));
83+
logger.info(
84+
pc.cyan(
85+
` Running ${options.flowCount} flows on a specific device (${options.device}) in real device mode.`,
86+
),
87+
);
88+
logger.info(
89+
pc.cyan(
90+
' Each flow runs in its own session on that device, which may be slow.',
91+
),
92+
);
93+
logger.info(pc.cyan(''));
94+
logger.info(pc.cyan(' Consider these alternatives for faster execution:'));
95+
logger.info(
96+
pc.cyan(
97+
` • Use ${pc.white('--shard-split <n>')} to run multiple flows in the same session`,
98+
),
99+
);
100+
logger.info(
101+
pc.cyan(
102+
` • Use wildcards for device (e.g., ${pc.white('"Pixel.*"')}) to parallelize across devices`,
103+
),
104+
);
105+
if (!this.isWildcardVersion(options.version)) {
106+
logger.info(
107+
pc.cyan(
108+
` • Use wildcards for version (e.g., ${pc.white('"15.*"')}) for broader device selection`,
109+
),
110+
);
111+
}
112+
logger.info(pc.cyan(border));
113+
logger.info('');
114+
},
115+
33116
/**
34117
* Check if a newer version is available and display update notice
35118
*/

tests/utils.test.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import logger from '../src/logger';
33

44
jest.mock('../src/logger', () => ({
55
warn: jest.fn(),
6+
info: jest.fn(),
67
}));
78

89
describe('utils', () => {
@@ -70,4 +71,118 @@ describe('utils', () => {
7071
expect(logger.warn).not.toHaveBeenCalled();
7172
});
7273
});
74+
75+
describe('isWildcardDevice', () => {
76+
it('should return true for undefined device', () => {
77+
expect(utils.isWildcardDevice(undefined)).toBe(true);
78+
});
79+
80+
it('should return true for wildcard "*"', () => {
81+
expect(utils.isWildcardDevice('*')).toBe(true);
82+
});
83+
84+
it('should return true for patterns containing "*"', () => {
85+
expect(utils.isWildcardDevice('Pixel*')).toBe(true);
86+
expect(utils.isWildcardDevice('Pixel 9*')).toBe(true);
87+
expect(utils.isWildcardDevice('*Pro')).toBe(true);
88+
});
89+
90+
it('should return true for patterns containing ".*"', () => {
91+
expect(utils.isWildcardDevice('Pixel.*')).toBe(true);
92+
expect(utils.isWildcardDevice('iPhone.*Pro')).toBe(true);
93+
});
94+
95+
it('should return true for patterns containing "?"', () => {
96+
expect(utils.isWildcardDevice('Pixel ?')).toBe(true);
97+
expect(utils.isWildcardDevice('iPhone 1?')).toBe(true);
98+
});
99+
100+
it('should return false for specific device names', () => {
101+
expect(utils.isWildcardDevice('Pixel 9 Pro')).toBe(false);
102+
expect(utils.isWildcardDevice('iPhone 15')).toBe(false);
103+
expect(utils.isWildcardDevice('Samsung Galaxy S24')).toBe(false);
104+
});
105+
});
106+
107+
describe('isWildcardVersion', () => {
108+
it('should return true for undefined version', () => {
109+
expect(utils.isWildcardVersion(undefined)).toBe(true);
110+
});
111+
112+
it('should return true for wildcard "*"', () => {
113+
expect(utils.isWildcardVersion('*')).toBe(true);
114+
});
115+
116+
it('should return true for patterns containing "*"', () => {
117+
expect(utils.isWildcardVersion('15*')).toBe(true);
118+
expect(utils.isWildcardVersion('15.*')).toBe(true);
119+
});
120+
121+
it('should return true for patterns containing "?"', () => {
122+
expect(utils.isWildcardVersion('15.?')).toBe(true);
123+
});
124+
125+
it('should return false for specific versions', () => {
126+
expect(utils.isWildcardVersion('15')).toBe(false);
127+
expect(utils.isWildcardVersion('15.0')).toBe(false);
128+
expect(utils.isWildcardVersion('15.0.1')).toBe(false);
129+
});
130+
});
131+
132+
describe('showRealDeviceFlowsInfo', () => {
133+
beforeEach(() => {
134+
jest.clearAllMocks();
135+
});
136+
137+
it('should not show info when not using real device', () => {
138+
utils.showRealDeviceFlowsInfo({
139+
realDevice: false,
140+
device: 'Pixel 9 Pro',
141+
flowCount: 5,
142+
});
143+
expect(logger.info).not.toHaveBeenCalled();
144+
});
145+
146+
it('should not show info when device is a wildcard', () => {
147+
utils.showRealDeviceFlowsInfo({
148+
realDevice: true,
149+
device: '*',
150+
flowCount: 5,
151+
});
152+
expect(logger.info).not.toHaveBeenCalled();
153+
154+
utils.showRealDeviceFlowsInfo({
155+
realDevice: true,
156+
device: 'Pixel.*',
157+
flowCount: 5,
158+
});
159+
expect(logger.info).not.toHaveBeenCalled();
160+
});
161+
162+
it('should not show info when flow count is 2 or less', () => {
163+
utils.showRealDeviceFlowsInfo({
164+
realDevice: true,
165+
device: 'Pixel 9 Pro',
166+
flowCount: 2,
167+
});
168+
expect(logger.info).not.toHaveBeenCalled();
169+
170+
utils.showRealDeviceFlowsInfo({
171+
realDevice: true,
172+
device: 'Pixel 9 Pro',
173+
flowCount: 1,
174+
});
175+
expect(logger.info).not.toHaveBeenCalled();
176+
});
177+
178+
it('should not show info when shardSplit is specified', () => {
179+
utils.showRealDeviceFlowsInfo({
180+
realDevice: true,
181+
device: 'Pixel 9 Pro',
182+
flowCount: 5,
183+
shardSplit: 2,
184+
});
185+
expect(logger.info).not.toHaveBeenCalled();
186+
});
187+
});
73188
});

0 commit comments

Comments
 (0)