craftdriver 1.1.0 → 1.1.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.
- package/CHANGELOG.md +7 -0
- package/README.md +1 -1
- package/dist/cli/snapshot.js +31 -4
- package/dist/cli/snapshot.js.map +1 -1
- package/dist/lib/browser.d.ts +6 -0
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +6 -0
- package/dist/lib/browser.js.map +1 -1
- package/dist/lib/by.d.ts +18 -0
- package/dist/lib/by.d.ts.map +1 -1
- package/dist/lib/by.js +141 -17
- package/dist/lib/by.js.map +1 -1
- package/dist/lib/locator.d.ts +28 -0
- package/dist/lib/locator.d.ts.map +1 -1
- package/dist/lib/locator.js +28 -0
- package/dist/lib/locator.js.map +1 -1
- package/docs/browser-api.md +4 -1
- package/docs/public/examples/a11y.html +44 -0
- package/docs/public/examples/clock.html +83 -0
- package/docs/public/examples/console-errors.html +344 -0
- package/docs/public/examples/dialogs.html +42 -0
- package/docs/public/examples/download.html +31 -0
- package/docs/public/examples/dynamic.html +131 -0
- package/docs/public/examples/emulate.html +127 -0
- package/docs/public/examples/evaluate.html +28 -0
- package/docs/public/examples/hover-select.html +295 -0
- package/docs/public/examples/iframe-child.html +51 -0
- package/docs/public/examples/iframes.html +35 -0
- package/docs/public/examples/keyboard.html +195 -0
- package/docs/public/examples/locator.html +131 -0
- package/docs/public/examples/login.html +116 -0
- package/docs/public/examples/mobile.html +270 -0
- package/docs/public/examples/mouse.html +505 -0
- package/docs/public/examples/network.html +293 -0
- package/docs/public/examples/popup-target.html +26 -0
- package/docs/public/examples/popup.html +50 -0
- package/docs/public/examples/screenshot.html +99 -0
- package/docs/public/examples/selectors.html +150 -0
- package/docs/public/examples/session.html +514 -0
- package/docs/public/examples/upload.html +36 -0
- package/docs/recipes/accessibility-gate.md +24 -40
- package/docs/recipes/console-error-gate.md +29 -38
- package/docs/recipes/file-upload-download.md +26 -43
- package/docs/recipes/find-elements.md +142 -0
- package/docs/recipes/login-once-reuse-session.md +24 -44
- package/docs/recipes/mobile-flow-with-network-and-logs.md +26 -41
- package/docs/recipes/mock-api-and-assert-network.md +22 -35
- package/docs/recipes/multi-user-contexts.md +32 -43
- package/docs/recipes/page-objects.md +52 -0
- package/docs/recipes/trace-failing-test.md +31 -44
- package/docs/recipes/virtual-clock-time-sensitive-ui.md +24 -40
- package/docs/recipes/vitest-browser-lifecycle.md +14 -11
- package/docs/recipes.md +20 -1
- package/docs/selectors.md +291 -229
- package/package.json +5 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<title>Locator Playground</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: system-ui, sans-serif;
|
|
10
|
+
margin: 20px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.product-list {
|
|
14
|
+
list-style: none;
|
|
15
|
+
padding: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.product {
|
|
19
|
+
border: 1px solid #ddd;
|
|
20
|
+
border-radius: 6px;
|
|
21
|
+
padding: 12px;
|
|
22
|
+
margin-bottom: 10px;
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
justify-content: space-between;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.product-name {
|
|
29
|
+
font-weight: 600;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.product-price {
|
|
33
|
+
color: #6b7280;
|
|
34
|
+
margin-left: 8px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.buy-btn {
|
|
38
|
+
padding: 6px 14px;
|
|
39
|
+
border-radius: 4px;
|
|
40
|
+
border: 1px solid #6366f1;
|
|
41
|
+
background: #eef2ff;
|
|
42
|
+
cursor: pointer;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#status {
|
|
46
|
+
margin-top: 16px;
|
|
47
|
+
font-size: 14px;
|
|
48
|
+
color: #374151;
|
|
49
|
+
}
|
|
50
|
+
</style>
|
|
51
|
+
</head>
|
|
52
|
+
|
|
53
|
+
<body>
|
|
54
|
+
<h1>Locator Playground</h1>
|
|
55
|
+
|
|
56
|
+
<!-- product list — five cards with stable ids and predictable text -->
|
|
57
|
+
<ul class="product-list" id="product-list">
|
|
58
|
+
<li class="product" id="product-1">
|
|
59
|
+
<span>
|
|
60
|
+
<span class="product-name">Widget Lite</span>
|
|
61
|
+
<span class="product-price">$9</span>
|
|
62
|
+
</span>
|
|
63
|
+
<button class="buy-btn" id="buy-1">Buy</button>
|
|
64
|
+
</li>
|
|
65
|
+
<li class="product" id="product-2">
|
|
66
|
+
<span>
|
|
67
|
+
<span class="product-name">Widget Pro</span>
|
|
68
|
+
<span class="product-price">$29</span>
|
|
69
|
+
</span>
|
|
70
|
+
<button class="buy-btn" id="buy-2">Buy</button>
|
|
71
|
+
</li>
|
|
72
|
+
<li class="product" id="product-3">
|
|
73
|
+
<span>
|
|
74
|
+
<span class="product-name">Widget Max</span>
|
|
75
|
+
<span class="product-price">$49</span>
|
|
76
|
+
</span>
|
|
77
|
+
<button class="buy-btn" id="buy-3">Buy</button>
|
|
78
|
+
</li>
|
|
79
|
+
<li class="product" id="product-4">
|
|
80
|
+
<span>
|
|
81
|
+
<span class="product-name">Gadget Basic</span>
|
|
82
|
+
<span class="product-price">$15</span>
|
|
83
|
+
</span>
|
|
84
|
+
<button class="buy-btn" id="buy-4">Buy</button>
|
|
85
|
+
</li>
|
|
86
|
+
<li class="product" id="product-5">
|
|
87
|
+
<span>
|
|
88
|
+
<span class="product-name">Gadget Plus</span>
|
|
89
|
+
<span class="product-price">$39</span>
|
|
90
|
+
</span>
|
|
91
|
+
<button class="buy-btn" id="buy-5">Buy</button>
|
|
92
|
+
</li>
|
|
93
|
+
</ul>
|
|
94
|
+
|
|
95
|
+
<!-- dynamic item — toggled in/out to test re-resolution -->
|
|
96
|
+
<button id="toggle-btn">Toggle dynamic item</button>
|
|
97
|
+
<div id="dynamic-container"></div>
|
|
98
|
+
|
|
99
|
+
<!-- click result area -->
|
|
100
|
+
<div id="status">idle</div>
|
|
101
|
+
|
|
102
|
+
<script>
|
|
103
|
+
// Mark which buy button was last clicked
|
|
104
|
+
document.querySelectorAll('.buy-btn').forEach(function (btn) {
|
|
105
|
+
btn.addEventListener('click', function () {
|
|
106
|
+
document.getElementById('status').textContent = 'clicked:' + btn.id;
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Toggle a dynamic product in and out of the DOM
|
|
111
|
+
var dynamicVisible = false;
|
|
112
|
+
document.getElementById('toggle-btn').addEventListener('click', function () {
|
|
113
|
+
var container = document.getElementById('dynamic-container');
|
|
114
|
+
if (dynamicVisible) {
|
|
115
|
+
container.innerHTML = '';
|
|
116
|
+
} else {
|
|
117
|
+
container.innerHTML =
|
|
118
|
+
'<li class="product" id="product-dynamic">' +
|
|
119
|
+
'<span><span class="product-name">Dynamic Item</span></span>' +
|
|
120
|
+
'<button class="buy-btn" id="buy-dynamic">Buy</button>' +
|
|
121
|
+
'</li>';
|
|
122
|
+
document.getElementById('buy-dynamic').addEventListener('click', function () {
|
|
123
|
+
document.getElementById('status').textContent = 'clicked:buy-dynamic';
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
dynamicVisible = !dynamicVisible;
|
|
127
|
+
});
|
|
128
|
+
</script>
|
|
129
|
+
</body>
|
|
130
|
+
|
|
131
|
+
</html>
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
|
+
<title>Craftdriver Login Example</title>
|
|
8
|
+
<style>
|
|
9
|
+
body {
|
|
10
|
+
font-family: system-ui, Arial, sans-serif;
|
|
11
|
+
margin: 2rem;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.card {
|
|
15
|
+
max-width: 420px;
|
|
16
|
+
border: 1px solid #ddd;
|
|
17
|
+
border-radius: 8px;
|
|
18
|
+
padding: 16px;
|
|
19
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
label {
|
|
23
|
+
display: block;
|
|
24
|
+
margin-top: 12px;
|
|
25
|
+
font-weight: 600;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
input {
|
|
29
|
+
width: 100%;
|
|
30
|
+
padding: 8px;
|
|
31
|
+
border: 1px solid #ccc;
|
|
32
|
+
border-radius: 6px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
button {
|
|
36
|
+
margin-top: 16px;
|
|
37
|
+
padding: 10px 14px;
|
|
38
|
+
border: none;
|
|
39
|
+
border-radius: 6px;
|
|
40
|
+
background: #2563eb;
|
|
41
|
+
color: #fff;
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
button:hover {
|
|
47
|
+
background: #1d4ed8;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#result {
|
|
51
|
+
margin-top: 16px;
|
|
52
|
+
font-weight: 600;
|
|
53
|
+
}
|
|
54
|
+
</style>
|
|
55
|
+
</head>
|
|
56
|
+
|
|
57
|
+
<body>
|
|
58
|
+
<div class="card">
|
|
59
|
+
<h1>Login</h1>
|
|
60
|
+
<form id="login-form">
|
|
61
|
+
<label for="username">Username</label>
|
|
62
|
+
<input id="username" name="username" type="text" autocomplete="off" />
|
|
63
|
+
<label for="password">Password</label>
|
|
64
|
+
<input id="password" name="password" type="password" autocomplete="off" />
|
|
65
|
+
<button id="submit" type="submit">Sign in</button>
|
|
66
|
+
</form>
|
|
67
|
+
<div id="result" aria-live="polite"></div>
|
|
68
|
+
</div>
|
|
69
|
+
<script>
|
|
70
|
+
const form = document.getElementById('login-form');
|
|
71
|
+
const result = document.getElementById('result');
|
|
72
|
+
|
|
73
|
+
// Check for existing session on page load
|
|
74
|
+
function checkSession() {
|
|
75
|
+
const sessionCookie = document.cookie.split(';').find(c => c.trim().startsWith('session='));
|
|
76
|
+
if (sessionCookie) {
|
|
77
|
+
const username = sessionCookie.split('=')[1];
|
|
78
|
+
showLoggedIn(decodeURIComponent(username));
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function showLoggedIn(username) {
|
|
85
|
+
form.style.display = 'none';
|
|
86
|
+
result.innerHTML = `<span id="welcome">Welcome back, ${username}!</span><br><button id="logout" onclick="logout()">Logout</button>`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function logout() {
|
|
90
|
+
document.cookie = 'session=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
|
|
91
|
+
location.reload();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check session on load
|
|
95
|
+
if (!checkSession()) {
|
|
96
|
+
form.addEventListener('submit', (e) => {
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
const u = document.getElementById('username').value;
|
|
99
|
+
const p = document.getElementById('password').value;
|
|
100
|
+
if (u && p) {
|
|
101
|
+
// Set session cookie (expires in 1 hour)
|
|
102
|
+
const expires = new Date(Date.now() + 3600000).toUTCString();
|
|
103
|
+
document.cookie = `session=${encodeURIComponent(u)}; path=/; expires=${expires}`;
|
|
104
|
+
// Persist UX prefs in localStorage so storageState round-trips capture them.
|
|
105
|
+
localStorage.setItem('lastUser', u);
|
|
106
|
+
localStorage.setItem('theme', 'dark');
|
|
107
|
+
showLoggedIn(u);
|
|
108
|
+
} else {
|
|
109
|
+
result.textContent = 'Missing credentials';
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
</script>
|
|
114
|
+
</body>
|
|
115
|
+
|
|
116
|
+
</html>
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Mobile Emulation Example</title>
|
|
8
|
+
<style>
|
|
9
|
+
* {
|
|
10
|
+
box-sizing: border-box;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
body {
|
|
14
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
15
|
+
margin: 0;
|
|
16
|
+
padding: 20px;
|
|
17
|
+
background: #f5f5f5;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.card {
|
|
21
|
+
background: white;
|
|
22
|
+
border-radius: 12px;
|
|
23
|
+
padding: 20px;
|
|
24
|
+
margin-bottom: 20px;
|
|
25
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
h1 {
|
|
29
|
+
color: #333;
|
|
30
|
+
font-size: 1.5rem;
|
|
31
|
+
margin-top: 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
h2 {
|
|
35
|
+
color: #666;
|
|
36
|
+
font-size: 1.1rem;
|
|
37
|
+
margin-top: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.device-info {
|
|
41
|
+
display: grid;
|
|
42
|
+
gap: 10px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.info-row {
|
|
46
|
+
display: flex;
|
|
47
|
+
justify-content: space-between;
|
|
48
|
+
padding: 8px 12px;
|
|
49
|
+
background: #f8f9fa;
|
|
50
|
+
border-radius: 6px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.info-label {
|
|
54
|
+
color: #666;
|
|
55
|
+
font-weight: 500;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.info-value {
|
|
59
|
+
color: #333;
|
|
60
|
+
font-family: monospace;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.touch-area {
|
|
64
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
65
|
+
border-radius: 12px;
|
|
66
|
+
padding: 40px;
|
|
67
|
+
text-align: center;
|
|
68
|
+
color: white;
|
|
69
|
+
cursor: pointer;
|
|
70
|
+
user-select: none;
|
|
71
|
+
-webkit-user-select: none;
|
|
72
|
+
touch-action: manipulation;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.touch-area:active {
|
|
76
|
+
transform: scale(0.98);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#touch-result {
|
|
80
|
+
margin-top: 15px;
|
|
81
|
+
padding: 10px;
|
|
82
|
+
background: #e9ecef;
|
|
83
|
+
border-radius: 6px;
|
|
84
|
+
font-family: monospace;
|
|
85
|
+
min-height: 40px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.responsive-indicator {
|
|
89
|
+
padding: 15px;
|
|
90
|
+
border-radius: 8px;
|
|
91
|
+
text-align: center;
|
|
92
|
+
font-weight: bold;
|
|
93
|
+
margin-bottom: 10px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.mobile-view {
|
|
97
|
+
background: #d4edda;
|
|
98
|
+
color: #155724;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.tablet-view {
|
|
102
|
+
background: #fff3cd;
|
|
103
|
+
color: #856404;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.desktop-view {
|
|
107
|
+
background: #cce5ff;
|
|
108
|
+
color: #004085;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@media (max-width: 480px) {
|
|
112
|
+
.responsive-indicator::before {
|
|
113
|
+
content: '📱 ';
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@media (min-width: 481px) and (max-width: 1024px) {
|
|
118
|
+
.responsive-indicator::before {
|
|
119
|
+
content: '📱 ';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@media (min-width: 1025px) {
|
|
124
|
+
.responsive-indicator::before {
|
|
125
|
+
content: '🖥️ ';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.orientation-box {
|
|
130
|
+
padding: 20px;
|
|
131
|
+
border-radius: 8px;
|
|
132
|
+
text-align: center;
|
|
133
|
+
font-size: 1.2rem;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.portrait {
|
|
137
|
+
background: #e2e3f3;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.landscape {
|
|
141
|
+
background: #f3e2e3;
|
|
142
|
+
}
|
|
143
|
+
</style>
|
|
144
|
+
</head>
|
|
145
|
+
|
|
146
|
+
<body>
|
|
147
|
+
<h1>📱 Mobile Emulation Test Page</h1>
|
|
148
|
+
|
|
149
|
+
<div class="card">
|
|
150
|
+
<h2>Device Information</h2>
|
|
151
|
+
<div class="device-info">
|
|
152
|
+
<div class="info-row">
|
|
153
|
+
<span class="info-label">Viewport Width</span>
|
|
154
|
+
<span class="info-value" id="viewport-width">-</span>
|
|
155
|
+
</div>
|
|
156
|
+
<div class="info-row">
|
|
157
|
+
<span class="info-label">Viewport Height</span>
|
|
158
|
+
<span class="info-value" id="viewport-height">-</span>
|
|
159
|
+
</div>
|
|
160
|
+
<div class="info-row">
|
|
161
|
+
<span class="info-label">Device Pixel Ratio</span>
|
|
162
|
+
<span class="info-value" id="pixel-ratio">-</span>
|
|
163
|
+
</div>
|
|
164
|
+
<div class="info-row">
|
|
165
|
+
<span class="info-label">Touch Support</span>
|
|
166
|
+
<span class="info-value" id="touch-support">-</span>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="info-row">
|
|
169
|
+
<span class="info-label">User Agent</span>
|
|
170
|
+
<span class="info-value" id="user-agent" style="word-break: break-all; font-size: 11px;">-</span>
|
|
171
|
+
</div>
|
|
172
|
+
<div class="info-row">
|
|
173
|
+
<span class="info-label">Platform</span>
|
|
174
|
+
<span class="info-value" id="platform">-</span>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<div class="card">
|
|
180
|
+
<h2>Responsive Layout Detection</h2>
|
|
181
|
+
<div id="responsive-indicator" class="responsive-indicator">Detecting...</div>
|
|
182
|
+
<div id="orientation-box" class="orientation-box">Checking orientation...</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<div class="card">
|
|
186
|
+
<h2>Touch Event Test</h2>
|
|
187
|
+
<div class="touch-area" id="touch-area">
|
|
188
|
+
Tap or Click Here
|
|
189
|
+
</div>
|
|
190
|
+
<div id="touch-result">Waiting for interaction...</div>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
<script>
|
|
194
|
+
// Update device information
|
|
195
|
+
function updateDeviceInfo() {
|
|
196
|
+
document.getElementById('viewport-width').textContent = window.innerWidth + 'px';
|
|
197
|
+
document.getElementById('viewport-height').textContent = window.innerHeight + 'px';
|
|
198
|
+
document.getElementById('pixel-ratio').textContent = window.devicePixelRatio;
|
|
199
|
+
document.getElementById('user-agent').textContent = navigator.userAgent;
|
|
200
|
+
document.getElementById('platform').textContent = navigator.platform || navigator.userAgentData?.platform || 'Unknown';
|
|
201
|
+
|
|
202
|
+
// Touch support detection
|
|
203
|
+
const hasTouch = 'ontouchstart' in window ||
|
|
204
|
+
navigator.maxTouchPoints > 0 ||
|
|
205
|
+
navigator.msMaxTouchPoints > 0;
|
|
206
|
+
document.getElementById('touch-support').textContent = hasTouch ? 'Yes ✓' : 'No ✗';
|
|
207
|
+
|
|
208
|
+
// Responsive indicator
|
|
209
|
+
const width = window.innerWidth;
|
|
210
|
+
const indicator = document.getElementById('responsive-indicator');
|
|
211
|
+
if (width <= 480) {
|
|
212
|
+
indicator.className = 'responsive-indicator mobile-view';
|
|
213
|
+
indicator.textContent = 'Mobile View (' + width + 'px)';
|
|
214
|
+
} else if (width <= 1024) {
|
|
215
|
+
indicator.className = 'responsive-indicator tablet-view';
|
|
216
|
+
indicator.textContent = 'Tablet View (' + width + 'px)';
|
|
217
|
+
} else {
|
|
218
|
+
indicator.className = 'responsive-indicator desktop-view';
|
|
219
|
+
indicator.textContent = 'Desktop View (' + width + 'px)';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Orientation
|
|
223
|
+
const orientationBox = document.getElementById('orientation-box');
|
|
224
|
+
if (window.innerHeight > window.innerWidth) {
|
|
225
|
+
orientationBox.className = 'orientation-box portrait';
|
|
226
|
+
orientationBox.textContent = '📱 Portrait Mode';
|
|
227
|
+
} else {
|
|
228
|
+
orientationBox.className = 'orientation-box landscape';
|
|
229
|
+
orientationBox.textContent = '📱 Landscape Mode';
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Touch area event handling
|
|
234
|
+
const touchArea = document.getElementById('touch-area');
|
|
235
|
+
const touchResult = document.getElementById('touch-result');
|
|
236
|
+
let eventLog = [];
|
|
237
|
+
|
|
238
|
+
function logEvent(type, details) {
|
|
239
|
+
const entry = `${type}: ${details}`;
|
|
240
|
+
eventLog.push(entry);
|
|
241
|
+
if (eventLog.length > 5) eventLog.shift();
|
|
242
|
+
touchResult.textContent = eventLog.join('\n');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
touchArea.addEventListener('touchstart', (e) => {
|
|
246
|
+
e.preventDefault();
|
|
247
|
+
const touch = e.touches[0];
|
|
248
|
+
logEvent('touchstart', `x=${Math.round(touch.clientX)}, y=${Math.round(touch.clientY)}`);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
touchArea.addEventListener('touchend', (e) => {
|
|
252
|
+
logEvent('touchend', 'Touch released');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
touchArea.addEventListener('click', (e) => {
|
|
256
|
+
logEvent('click', `x=${Math.round(e.clientX)}, y=${Math.round(e.clientY)}`);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
touchArea.addEventListener('mousedown', (e) => {
|
|
260
|
+
logEvent('mousedown', `x=${Math.round(e.clientX)}, y=${Math.round(e.clientY)}`);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Initial update and resize listener
|
|
264
|
+
updateDeviceInfo();
|
|
265
|
+
window.addEventListener('resize', updateDeviceInfo);
|
|
266
|
+
window.addEventListener('orientationchange', updateDeviceInfo);
|
|
267
|
+
</script>
|
|
268
|
+
</body>
|
|
269
|
+
|
|
270
|
+
</html>
|