Device emulation, input simulation, and testing utilities for cross-platform development. This covers domains that enable testing and debugging across different devices, orientations, and interaction modes.
Device and browser emulation for testing responsive designs and mobile experiences.
namespace Protocol.Emulation {
interface ScreenOrientation {
type: ('portraitPrimary' | 'portraitSecondary' | 'landscapePrimary' | 'landscapeSecondary');
angle: integer;
}
interface DisplayFeature {
orientation: ('vertical' | 'horizontal');
offset: integer;
maskLength: integer;
}
interface MediaFeature {
name: string;
value: string;
}
interface VirtualTimePolicy = ('advance' | 'pause' | 'pauseIfNetworkFetchesPending');
interface UserAgentBrandVersion {
brand: string;
version: string;
}
interface UserAgentMetadata {
brands?: UserAgentBrandVersion[];
fullVersionList?: UserAgentBrandVersion[];
platform?: string;
platformVersion?: string;
architecture?: string;
model?: string;
mobile?: boolean;
bitness?: string;
wow64?: boolean;
}
interface SetDeviceMetricsOverrideRequest {
width: integer;
height: integer;
deviceScaleFactor: number;
mobile: boolean;
scale?: number;
screenWidth?: integer;
screenHeight?: integer;
positionX?: integer;
positionY?: integer;
dontSetVisibleSize?: boolean;
screenOrientation?: ScreenOrientation;
viewport?: Page.Viewport;
displayFeature?: DisplayFeature;
devicePosture?: DevicePosture;
}
interface SetGeolocationOverrideRequest {
latitude?: number;
longitude?: number;
accuracy?: number;
}
interface SetUserAgentOverrideRequest {
userAgent: string;
acceptLanguage?: string;
platform?: string;
userAgentMetadata?: UserAgentMetadata;
}
interface SetTimezoneOverrideRequest {
timezoneId: string;
}
interface SetLocaleOverrideRequest {
locale?: string;
}
interface SetVirtualTimePolicyRequest {
policy: VirtualTimePolicy;
budget?: number;
maxVirtualTimeTaskStarvationCount?: integer;
initialVirtualTime?: Network.TimeSinceEpoch;
}
interface SetVirtualTimePolicyResponse {
virtualTimeTicksBase: number;
}
interface SetPageScaleFactorRequest {
pageScaleFactor: number;
}
interface SetScrollbarsHiddenRequest {
hidden: boolean;
}
interface SetDocumentCookieDisabledRequest {
disabled: boolean;
}
interface SetEmitTouchEventsForMouseRequest {
enabled: boolean;
configuration?: ('mobile' | 'desktop');
}
interface SetEmulatedMediaRequest {
media?: string;
features?: MediaFeature[];
}
interface SetCPUThrottlingRateRequest {
rate: number;
}
interface SetFocusEmulationEnabledRequest {
enabled: boolean;
}
interface SetAutoDarkModeOverrideRequest {
enabled?: boolean;
}
interface VirtualTimeBudgetExpiredEvent {}
interface VirtualTimeAdvancedEvent {
virtualTimeElapsed: number;
}
interface VirtualTimePausedEvent {
virtualTimeElapsed: number;
}
}Usage Example:
import Protocol from "devtools-protocol/types/protocol";
class DeviceEmulator {
async emulateDevice(deviceConfig: DeviceConfig): Promise<void> {
// Set device metrics
const metricsRequest: Protocol.Emulation.SetDeviceMetricsOverrideRequest = {
width: deviceConfig.width,
height: deviceConfig.height,
deviceScaleFactor: deviceConfig.deviceScaleFactor,
mobile: deviceConfig.mobile,
screenOrientation: {
type: deviceConfig.orientation,
angle: deviceConfig.orientationAngle
}
};
// Set user agent
const userAgentRequest: Protocol.Emulation.SetUserAgentOverrideRequest = {
userAgent: deviceConfig.userAgent,
platform: deviceConfig.platform,
userAgentMetadata: {
brands: deviceConfig.brands,
mobile: deviceConfig.mobile,
platform: deviceConfig.platform
}
};
// Set geolocation if provided
if (deviceConfig.geolocation) {
const geoRequest: Protocol.Emulation.SetGeolocationOverrideRequest = {
latitude: deviceConfig.geolocation.latitude,
longitude: deviceConfig.geolocation.longitude,
accuracy: deviceConfig.geolocation.accuracy
};
// Implementation would send Emulation.setGeolocationOverride
}
// Set timezone
const timezoneRequest: Protocol.Emulation.SetTimezoneOverrideRequest = {
timezoneId: deviceConfig.timezone || "UTC"
};
// Implementation would send respective emulation commands
}
async setupNetworkThrottling(profile: NetworkThrottlingProfile): Promise<void> {
// Set CPU throttling
const cpuRequest: Protocol.Emulation.SetCPUThrottlingRateRequest = {
rate: profile.cpuThrottling
};
// Implementation would send Emulation.setCPUThrottlingRate
}
async enableTouchEmulation(enabled: boolean): Promise<void> {
const touchRequest: Protocol.Emulation.SetEmitTouchEventsForMouseRequest = {
enabled,
configuration: 'mobile'
};
// Implementation would send Emulation.setEmitTouchEventsForMouse
}
async emulateMediaFeatures(features: Protocol.Emulation.MediaFeature[]): Promise<void> {
const mediaRequest: Protocol.Emulation.SetEmulatedMediaRequest = {
features
};
// Implementation would send Emulation.setEmulatedMedia
}
setupVirtualTimeControl(): VirtualTimeController {
return new VirtualTimeController();
}
}
class VirtualTimeController {
async pauseVirtualTime(): Promise<void> {
const request: Protocol.Emulation.SetVirtualTimePolicyRequest = {
policy: 'pause'
};
// Implementation would send Emulation.setVirtualTimePolicy
}
async advanceVirtualTime(milliseconds: number): Promise<void> {
const request: Protocol.Emulation.SetVirtualTimePolicyRequest = {
policy: 'advance',
budget: milliseconds
};
// Implementation would send Emulation.setVirtualTimePolicy
}
setupVirtualTimeListeners(): void {
this.onVirtualTimeBudgetExpired = (event: Protocol.Emulation.VirtualTimeBudgetExpiredEvent) => {
console.log("Virtual time budget expired");
};
this.onVirtualTimeAdvanced = (event: Protocol.Emulation.VirtualTimeAdvancedEvent) => {
console.log(`Virtual time advanced by ${event.virtualTimeElapsed}ms`);
};
}
}
interface DeviceConfig {
width: number;
height: number;
deviceScaleFactor: number;
mobile: boolean;
orientation: 'portraitPrimary' | 'portraitSecondary' | 'landscapePrimary' | 'landscapeSecondary';
orientationAngle: number;
userAgent: string;
platform: string;
brands?: Protocol.Emulation.UserAgentBrandVersion[];
geolocation?: {
latitude: number;
longitude: number;
accuracy: number;
};
timezone?: string;
}
interface NetworkThrottlingProfile {
cpuThrottling: number;
downloadThroughput?: number;
uploadThroughput?: number;
latency?: number;
}Input event simulation and handling for automated testing and interaction simulation.
namespace Protocol.Input {
type TouchPoint = {
id: integer;
x: number;
y: number;
radiusX?: number;
radiusY?: number;
rotationAngle?: number;
force?: number;
};
type GestureSourceType = ('default' | 'touch' | 'mouse');
type MouseButton = ('none' | 'left' | 'middle' | 'right' | 'back' | 'forward');
type KeyboardModifiers = integer;
interface DispatchKeyEventRequest {
type: ('keyDown' | 'keyUp' | 'rawKeyDown' | 'char');
modifiers?: integer;
timestamp?: Network.TimeSinceEpoch;
text?: string;
unmodifiedText?: string;
keyIdentifier?: string;
code?: string;
key?: string;
windowsVirtualKeyCode?: integer;
nativeVirtualKeyCode?: integer;
autoRepeat?: boolean;
isKeypad?: boolean;
isSystemKey?: boolean;
location?: integer;
commands?: string[];
}
interface DispatchMouseEventRequest {
type: ('mousePressed' | 'mouseReleased' | 'mouseMoved' | 'mouseWheel');
x: number;
y: number;
modifiers?: integer;
timestamp?: Network.TimeSinceEpoch;
button?: MouseButton;
buttons?: integer;
clickCount?: integer;
force?: number;
tangentialPressure?: number;
tiltX?: integer;
tiltY?: integer;
twist?: integer;
deltaX?: number;
deltaY?: number;
pointerType?: ('mouse' | 'pen');
}
interface DispatchTouchEventRequest {
type: ('touchStart' | 'touchEnd' | 'touchMove' | 'touchCancel');
touchPoints: TouchPoint[];
modifiers?: integer;
timestamp?: Network.TimeSinceEpoch;
}
interface EmulateTouchFromMouseEventRequest {
type: ('mousePressed' | 'mouseReleased' | 'mouseMoved' | 'mouseWheel');
x: integer;
y: integer;
button: MouseButton;
timestamp?: Network.TimeSinceEpoch;
deltaX?: number;
deltaY?: number;
modifiers?: integer;
clickCount?: integer;
}
interface SynthesizePinchGestureRequest {
x: number;
y: number;
scaleFactor: number;
relativeSpeed?: integer;
gestureSourceType?: GestureSourceType;
}
interface SynthesizeScrollGestureRequest {
x: number;
y: number;
xDistance?: number;
yDistance?: number;
xOverscroll?: number;
yOverscroll?: number;
preventFling?: boolean;
speed?: integer;
gestureSourceType?: GestureSourceType;
repeatCount?: integer;
repeatDelayMs?: integer;
interactionMarkerName?: string;
}
interface SynthesizeTapGestureRequest {
x: number;
y: number;
duration?: integer;
tapCount?: integer;
gestureSourceType?: GestureSourceType;
}
interface SetIgnoreInputEventsRequest {
ignore: boolean;
}
interface SetInterceptDragsRequest {
enabled: boolean;
}
interface DragDataItem {
mimeType: string;
data: string;
title?: string;
baseURL?: string;
}
interface DispatchDragEventRequest {
type: ('dragEnter' | 'dragOver' | 'drop' | 'dragCancel');
x: number;
y: number;
data: DragDataItem[];
modifiers?: integer;
}
}Usage Example:
import Protocol from "devtools-protocol/types/protocol";
class InputSimulator {
async clickElement(x: number, y: number): Promise<void> {
// Mouse press
const pressRequest: Protocol.Input.DispatchMouseEventRequest = {
type: 'mousePressed',
x,
y,
button: 'left',
clickCount: 1
};
// Mouse release
const releaseRequest: Protocol.Input.DispatchMouseEventRequest = {
type: 'mouseReleased',
x,
y,
button: 'left',
clickCount: 1
};
// Implementation would send Input.dispatchMouseEvent for both events
}
async typeText(text: string): Promise<void> {
for (const char of text) {
// Key down
const keyDownRequest: Protocol.Input.DispatchKeyEventRequest = {
type: 'keyDown',
text: char,
key: char,
code: `Key${char.toUpperCase()}`
};
// Key up
const keyUpRequest: Protocol.Input.DispatchKeyEventRequest = {
type: 'keyUp',
text: char,
key: char,
code: `Key${char.toUpperCase()}`
};
// Implementation would send Input.dispatchKeyEvent for both events
await this.delay(50); // Small delay between characters
}
}
async performTouchGesture(gesture: TouchGesture): Promise<void> {
switch (gesture.type) {
case 'tap':
await this.tap(gesture.x, gesture.y);
break;
case 'scroll':
await this.scroll(gesture.x, gesture.y, gesture.deltaX!, gesture.deltaY!);
break;
case 'pinch':
await this.pinch(gesture.x, gesture.y, gesture.scaleFactor!);
break;
}
}
private async tap(x: number, y: number): Promise<void> {
const tapRequest: Protocol.Input.SynthesizeTapGestureRequest = {
x,
y,
duration: 50,
tapCount: 1,
gestureSourceType: 'touch'
};
// Implementation would send Input.synthesizeTapGesture
}
private async scroll(x: number, y: number, deltaX: number, deltaY: number): Promise<void> {
const scrollRequest: Protocol.Input.SynthesizeScrollGestureRequest = {
x,
y,
xDistance: deltaX,
yDistance: deltaY,
gestureSourceType: 'touch'
};
// Implementation would send Input.synthesizeScrollGesture
}
private async pinch(x: number, y: number, scaleFactor: number): Promise<void> {
const pinchRequest: Protocol.Input.SynthesizePinchGestureRequest = {
x,
y,
scaleFactor,
gestureSourceType: 'touch'
};
// Implementation would send Input.synthesizePinchGesture
}
async simulateKeyboardShortcut(modifiers: string[], key: string): Promise<void> {
let modifierBits = 0;
// Convert modifier strings to bits
if (modifiers.includes('ctrl')) modifierBits |= 2;
if (modifiers.includes('shift')) modifierBits |= 8;
if (modifiers.includes('alt')) modifierBits |= 1;
if (modifiers.includes('meta')) modifierBits |= 4;
const keyRequest: Protocol.Input.DispatchKeyEventRequest = {
type: 'keyDown',
modifiers: modifierBits,
key,
code: `Key${key.toUpperCase()}`
};
// Implementation would send Input.dispatchKeyEvent
}
async enableInputInterception(enabled: boolean): Promise<void> {
const request: Protocol.Input.SetIgnoreInputEventsRequest = {
ignore: !enabled
};
// Implementation would send Input.setIgnoreInputEvents
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
interface TouchGesture {
type: 'tap' | 'scroll' | 'pinch';
x: number;
y: number;
deltaX?: number;
deltaY?: number;
scaleFactor?: number;
}Device orientation emulation for testing responsive designs with different orientations.
namespace Protocol.DeviceOrientation {
interface SetDeviceOrientationOverrideRequest {
alpha: number;
beta: number;
gamma: number;
}
}Bluetooth device emulation for testing Bluetooth-related functionality.
namespace Protocol.BluetoothEmulation {
interface CentralState = ('absent' | 'powered-off' | 'powered-on');
interface ManufacturerData {
key: integer;
data: string;
}
interface ServiceData {
uuid: string;
data: string;
}
interface ScanRecord {
deviceName?: string;
uuids?: string[];
appearance?: integer;
txPower?: integer;
manufacturerData?: ManufacturerData[];
serviceData?: ServiceData[];
}
interface ScanEntry {
deviceAddress: string;
rssi: integer;
scanRecord: ScanRecord;
}
interface EnableRequest {
state: CentralState;
}
interface SimulatePreconnectedPeripheralRequest {
address: string;
name: string;
manufacturerId: integer;
knownServiceUuids: string[];
}
interface SimulateAdvertisementRequest {
entry: ScanEntry;
}
}import Protocol from "devtools-protocol/types/protocol";
class DeviceTesting {
private emulator: DeviceEmulator;
private inputSimulator: InputSimulator;
constructor() {
this.emulator = new DeviceEmulator();
this.inputSimulator = new InputSimulator();
}
async testResponsiveDesign(): Promise<TestResults> {
const results: TestResults = {
devices: [],
issues: []
};
const deviceConfigs = this.getCommonDeviceConfigs();
for (const device of deviceConfigs) {
console.log(`Testing device: ${device.name}`);
// Emulate device
await this.emulator.emulateDevice(device.config);
// Take screenshot
// Implementation would capture screenshot
// Test touch interactions
const touchResults = await this.testTouchInteractions();
// Test form inputs
const formResults = await this.testFormInputs();
// Test gestures
const gestureResults = await this.testGestures();
results.devices.push({
name: device.name,
touchResults,
formResults,
gestureResults,
screenshot: `screenshot_${device.name}.png`
});
}
return results;
}
private getCommonDeviceConfigs(): Array<{ name: string; config: DeviceConfig }> {
return [
{
name: 'iPhone 12',
config: {
width: 390,
height: 844,
deviceScaleFactor: 3,
mobile: true,
orientation: 'portraitPrimary',
orientationAngle: 0,
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15',
platform: 'iPhone'
}
},
{
name: 'iPad Air',
config: {
width: 820,
height: 1180,
deviceScaleFactor: 2,
mobile: true,
orientation: 'portraitPrimary',
orientationAngle: 0,
userAgent: 'Mozilla/5.0 (iPad; CPU OS 14_0 like Mac OS X) AppleWebKit/605.1.15',
platform: 'iPad'
}
},
{
name: 'Samsung Galaxy S21',
config: {
width: 384,
height: 854,
deviceScaleFactor: 2.75,
mobile: true,
orientation: 'portraitPrimary',
orientationAngle: 0,
userAgent: 'Mozilla/5.0 (Linux; Android 11; SM-G991B) AppleWebKit/537.36',
platform: 'Linux armv7l'
}
}
];
}
private async testTouchInteractions(): Promise<TouchTestResults> {
const results: TouchTestResults = {
tapAccuracy: 0,
scrollSmoothness: 0,
pinchZoomWorks: false
};
// Test tap accuracy
const tapTargets = [
{ x: 100, y: 100 },
{ x: 200, y: 200 },
{ x: 300, y: 300 }
];
let successfulTaps = 0;
for (const target of tapTargets) {
try {
await this.inputSimulator.performTouchGesture({
type: 'tap',
x: target.x,
y: target.y
});
successfulTaps++;
} catch (error) {
console.error(`Tap failed at ${target.x}, ${target.y}:`, error);
}
}
results.tapAccuracy = successfulTaps / tapTargets.length;
// Test scroll smoothness
try {
await this.inputSimulator.performTouchGesture({
type: 'scroll',
x: 200,
y: 400,
deltaX: 0,
deltaY: -200
});
results.scrollSmoothness = 1; // Simplified - would measure actual smoothness
} catch (error) {
results.scrollSmoothness = 0;
}
// Test pinch zoom
try {
await this.inputSimulator.performTouchGesture({
type: 'pinch',
x: 200,
y: 300,
scaleFactor: 2
});
results.pinchZoomWorks = true;
} catch (error) {
results.pinchZoomWorks = false;
}
return results;
}
private async testFormInputs(): Promise<FormTestResults> {
const results: FormTestResults = {
textInputWorks: false,
keyboardShortcutsWork: false,
virtualKeyboardShows: false
};
// Test text input
try {
await this.inputSimulator.clickElement(200, 200); // Click on input field
await this.inputSimulator.typeText("Test input");
results.textInputWorks = true;
} catch (error) {
results.textInputWorks = false;
}
// Test keyboard shortcuts
try {
await this.inputSimulator.simulateKeyboardShortcut(['ctrl'], 'a');
results.keyboardShortcutsWork = true;
} catch (error) {
results.keyboardShortcutsWork = false;
}
return results;
}
private async testGestures(): Promise<GestureTestResults> {
return {
swipeWorks: true, // Simplified implementation
longPressWorks: true,
multiTouchWorks: true
};
}
async testNetworkConditions(): Promise<void> {
const networkProfiles = [
{ name: 'Fast 3G', cpuThrottling: 4 },
{ name: 'Slow 3G', cpuThrottling: 6 },
{ name: 'Offline', cpuThrottling: 1 }
];
for (const profile of networkProfiles) {
console.log(`Testing network condition: ${profile.name}`);
await this.emulator.setupNetworkThrottling(profile);
// Test page load performance under these conditions
// Implementation would measure load times and performance metrics
}
}
async testAccessibility(): Promise<AccessibilityResults> {
// Enable focus emulation for accessibility testing
await this.emulator.enableTouchEmulation(false);
// Test keyboard navigation
const keyboardNavResults = await this.testKeyboardNavigation();
// Test screen reader compatibility
const screenReaderResults = await this.testScreenReaderCompatibility();
return {
keyboardNavigation: keyboardNavResults,
screenReader: screenReaderResults
};
}
private async testKeyboardNavigation(): Promise<boolean> {
// Test tab navigation
try {
await this.inputSimulator.simulateKeyboardShortcut([], 'Tab');
await this.inputSimulator.simulateKeyboardShortcut(['shift'], 'Tab');
return true;
} catch (error) {
return false;
}
}
private async testScreenReaderCompatibility(): Promise<boolean> {
// This would involve checking ARIA attributes and semantic HTML
// Simplified implementation
return true;
}
}
interface TestResults {
devices: DeviceTestResult[];
issues: string[];
}
interface DeviceTestResult {
name: string;
touchResults: TouchTestResults;
formResults: FormTestResults;
gestureResults: GestureTestResults;
screenshot: string;
}
interface TouchTestResults {
tapAccuracy: number;
scrollSmoothness: number;
pinchZoomWorks: boolean;
}
interface FormTestResults {
textInputWorks: boolean;
keyboardShortcutsWork: boolean;
virtualKeyboardShows: boolean;
}
interface GestureTestResults {
swipeWorks: boolean;
longPressWorks: boolean;
multiTouchWorks: boolean;
}
interface AccessibilityResults {
keyboardNavigation: boolean;
screenReader: boolean;
}import Protocol from "devtools-protocol/types/protocol";
class AutomatedTester {
private inputSimulator: InputSimulator;
constructor() {
this.inputSimulator = new InputSimulator();
}
async runTestSuite(tests: TestCase[]): Promise<TestSuiteResults> {
const results: TestSuiteResults = {
passed: 0,
failed: 0,
details: []
};
for (const test of tests) {
console.log(`Running test: ${test.name}`);
try {
const startTime = Date.now();
await this.executeTest(test);
const endTime = Date.now();
results.passed++;
results.details.push({
name: test.name,
status: 'passed',
duration: endTime - startTime
});
} catch (error) {
results.failed++;
results.details.push({
name: test.name,
status: 'failed',
error: error.message,
duration: 0
});
}
}
return results;
}
private async executeTest(test: TestCase): Promise<void> {
for (const action of test.actions) {
await this.executeAction(action);
if (action.waitAfter) {
await this.delay(action.waitAfter);
}
}
}
private async executeAction(action: TestAction): Promise<void> {
switch (action.type) {
case 'click':
await this.inputSimulator.clickElement(action.x!, action.y!);
break;
case 'type':
await this.inputSimulator.typeText(action.text!);
break;
case 'scroll':
await this.inputSimulator.performTouchGesture({
type: 'scroll',
x: action.x!,
y: action.y!,
deltaX: action.deltaX,
deltaY: action.deltaY
});
break;
case 'wait':
await this.delay(action.duration!);
break;
}
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
interface TestCase {
name: string;
actions: TestAction[];
}
interface TestAction {
type: 'click' | 'type' | 'scroll' | 'wait';
x?: number;
y?: number;
text?: string;
deltaX?: number;
deltaY?: number;
duration?: number;
waitAfter?: number;
}
interface TestSuiteResults {
passed: number;
failed: number;
details: TestResult[];
}
interface TestResult {
name: string;
status: 'passed' | 'failed';
duration: number;
error?: string;
}