Tests / Test apps.device-host-agent.tests.test_mcp_token.test_load_or_create_concurrent_calls_do_not_corrupt failed
204 lines
11 KiB
HTML
204 lines
11 KiB
HTML
{% extends "base.html" %}
|
|
{% block body %}
|
|
<h1>Devices</h1>
|
|
{% if error %}
|
|
<p class="error">{{ error }}</p>
|
|
{% endif %}
|
|
<table>
|
|
<thead><tr><th>ID</th><th>Name</th><th>Driver</th><th>Cloud ID</th><th>Screenshot</th><th></th></tr></thead>
|
|
<tbody>{% for device in devices %}
|
|
<tr>
|
|
<td>{{ device["device_id"] }}</td>
|
|
<td>{{ device["name"] or "" }}</td>
|
|
<td>{{ device["driver_type"] }}</td>
|
|
<td>{{ device["cloud_device_id"] or "" }}</td>
|
|
<td>
|
|
<button type="button" class="screenshot-button" data-device-id="{{ device["device_id"] }}">Get screenshot</button>
|
|
<div class="screenshot-preview" data-screenshot-preview hidden>
|
|
<p class="screenshot-status" data-screenshot-status></p>
|
|
<img alt="Current screen for {{ device["device_id"] }}" data-screenshot-image hidden>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<a href="/devices?edit={{ device["device_id"] }}">Edit</a>
|
|
<form class="inline" method="post" action="/devices/remove">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<input type="hidden" name="device_id" value="{{ device["device_id"] }}">
|
|
<button type="submit">Remove</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}</tbody>
|
|
</table>
|
|
<h2>{{ "Edit device" if edit_record else "Add device" }}</h2>
|
|
<button type="button" id="discover-ios">Scan connected iPhones</button>
|
|
<p id="discovery-status" class="screenshot-status"></p>
|
|
<div id="discovered-devices"></div>
|
|
<form method="post" action="/devices/save">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<label>Device ID <input type="text" name="device_id" value="{{ edit_record["device_id"] if edit_record else "" }}" required></label><br>
|
|
<label>Name <input type="text" name="name" value="{{ edit_record["name"] if edit_record else "" }}"></label><br>
|
|
<label>Platform and protocol
|
|
<select name="driver_type" id="driver-type" required>
|
|
<option value="wda"{% if not edit_record or edit_record["driver_type"] == "wda" %} selected{% endif %}>iOS - Appium / XCUITest (WDA)</option>
|
|
<option value="uiautomator2"{% if edit_record and edit_record["driver_type"] == "uiautomator2" %} selected{% endif %}>Android - Appium / UiAutomator2</option>
|
|
</select>
|
|
</label><br>
|
|
<label>Appium server URL <input type="url" name="server_url" value="{{ edit_record["connection_info"].get("server_url", "http://127.0.0.1:4723") if edit_record else "http://127.0.0.1:4723" }}" required></label><br>
|
|
<label>Device UDID <input type="text" name="udid" value="{{ edit_record["connection_info"].get("udid", "") if edit_record else "" }}" required></label><br>
|
|
<label>Device name <input type="text" name="device_name" value="{{ edit_record["connection_info"].get("device_name", "") if edit_record else "" }}" placeholder="iPhone"></label><br>
|
|
<label class="ios-setting">WDA local port <input type="number" min="1" max="65535" name="wda_local_port" value="{{ edit_record["connection_info"].get("wda_local_port", "") if edit_record else "" }}" placeholder="8100"></label>
|
|
<label class="android-setting">UiAutomator2 system port <input type="number" min="1" max="65535" name="system_port" value="{{ edit_record["connection_info"].get("system_port", "") if edit_record else "" }}" placeholder="8200"></label><br>
|
|
<details>
|
|
<summary>Advanced connection capabilities (JSON)</summary>
|
|
<textarea name="connection_info" rows="4" cols="60">{{ connection_info_json }}</textarea>
|
|
</details>
|
|
<p id="connection-test-status" class="screenshot-status"></p>
|
|
<button type="button" id="test-connection">Test connection</button>
|
|
<button type="submit">Save</button>
|
|
</form>
|
|
<style>
|
|
.screenshot-preview { margin-top: 0.5rem; max-width: 260px; }
|
|
.screenshot-preview img { display: block; width: 100%; height: auto; border: 1px solid #c8d0d6; }
|
|
.screenshot-status { margin: 0 0 0.35rem; color: #5e6b73; }
|
|
.screenshot-status.error { color: #b00020; }
|
|
</style>
|
|
<script>
|
|
(() => {
|
|
const driverType = document.getElementById("driver-type");
|
|
const syncPlatformFields = () => {
|
|
const ios = driverType.value === "wda";
|
|
document.querySelectorAll(".ios-setting").forEach((el) => { el.hidden = !ios; });
|
|
document.querySelectorAll(".android-setting").forEach((el) => { el.hidden = ios; });
|
|
};
|
|
driverType.addEventListener("change", syncPlatformFields);
|
|
syncPlatformFields();
|
|
const csrfInput = document.querySelector('input[name="csrf_token"]');
|
|
const csrfToken = csrfInput ? csrfInput.value : "";
|
|
const form = document.querySelector('form[action="/devices/save"]');
|
|
const discoverButton = document.getElementById("discover-ios");
|
|
const discoveryStatus = document.getElementById("discovery-status");
|
|
const discoveredDevices = document.getElementById("discovered-devices");
|
|
discoverButton.addEventListener("click", async () => {
|
|
discoverButton.disabled = true;
|
|
discoveryStatus.classList.remove("error");
|
|
discoveryStatus.textContent = "Scanning...";
|
|
discoveredDevices.replaceChildren();
|
|
try {
|
|
const response = await fetch("/api/devices/discover-ios");
|
|
const payload = await response.json();
|
|
if (!response.ok) throw new Error(payload.detail || "Discovery failed.");
|
|
discoveryStatus.textContent = payload.devices.length
|
|
? `Found ${payload.devices.length} connected iPhone(s).`
|
|
: "No connected, paired iPhones found.";
|
|
payload.devices.forEach((device, index) => {
|
|
const row = document.createElement("p");
|
|
const description = document.createElement("span");
|
|
description.textContent = `${device.name} - ${device.model} - iOS ${device.os_version} (${device.transport}) `;
|
|
const select = document.createElement("button");
|
|
select.type = "button";
|
|
select.textContent = device.configured ? "Already added" : "Use this iPhone";
|
|
select.disabled = device.configured;
|
|
select.addEventListener("click", () => {
|
|
form.elements.driver_type.value = "wda";
|
|
form.elements.udid.value = device.udid;
|
|
form.elements.device_name.value = device.name;
|
|
form.elements.wda_local_port.value = device.suggested_wda_port;
|
|
form.elements.device_id.value ||= `ios-phone-${index + 1}`;
|
|
form.elements.name.value ||= device.name;
|
|
const advanced = JSON.parse(form.elements.connection_info.value || "{}");
|
|
advanced.mjpegServerPort = device.suggested_mjpeg_port;
|
|
advanced.derivedDataPath = `/tmp/wda-${device.udid}`;
|
|
form.elements.connection_info.value = JSON.stringify(advanced, null, 2);
|
|
syncPlatformFields();
|
|
form.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
});
|
|
row.append(description, select);
|
|
discoveredDevices.append(row);
|
|
});
|
|
} catch (error) {
|
|
discoveryStatus.classList.add("error");
|
|
discoveryStatus.textContent = error.message || "Discovery failed.";
|
|
} finally {
|
|
discoverButton.disabled = false;
|
|
}
|
|
});
|
|
const testButton = document.getElementById("test-connection");
|
|
const testStatus = document.getElementById("connection-test-status");
|
|
const connectionInfo = () => {
|
|
const data = new FormData(form);
|
|
let info = {};
|
|
const advanced = String(data.get("connection_info") || "{}");
|
|
info = JSON.parse(advanced);
|
|
["server_url", "udid", "device_name"].forEach((key) => {
|
|
const value = String(data.get(key) || "").trim();
|
|
if (value) info[key] = value;
|
|
});
|
|
const portKey = data.get("driver_type") === "wda" ? "wda_local_port" : "system_port";
|
|
const port = String(data.get(portKey) || "").trim();
|
|
if (port) info[portKey] = Number(port);
|
|
return { driver_type: data.get("driver_type"), connection_info: info };
|
|
};
|
|
testButton.addEventListener("click", async () => {
|
|
testButton.disabled = true;
|
|
testStatus.classList.remove("error");
|
|
testStatus.textContent = "Testing connection...";
|
|
try {
|
|
const response = await fetch("/api/devices/test-connection", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken },
|
|
body: JSON.stringify(connectionInfo()),
|
|
});
|
|
const payload = await response.json();
|
|
if (!response.ok) throw new Error(payload.detail || "Connection test failed.");
|
|
testStatus.textContent = "Connection successful.";
|
|
} catch (error) {
|
|
testStatus.classList.add("error");
|
|
testStatus.textContent = error.message || "Connection test failed.";
|
|
} finally {
|
|
testButton.disabled = false;
|
|
}
|
|
});
|
|
document.querySelectorAll(".screenshot-button").forEach((button) => {
|
|
button.addEventListener("click", async () => {
|
|
const deviceId = button.dataset.deviceId;
|
|
const preview = button.parentElement.querySelector("[data-screenshot-preview]");
|
|
const status = preview.querySelector("[data-screenshot-status]");
|
|
const image = preview.querySelector("[data-screenshot-image]");
|
|
const previousUrl = image.dataset.objectUrl;
|
|
if (previousUrl) URL.revokeObjectURL(previousUrl);
|
|
button.disabled = true;
|
|
preview.hidden = false;
|
|
image.hidden = true;
|
|
status.classList.remove("error");
|
|
status.textContent = "Capturing...";
|
|
try {
|
|
const response = await fetch(
|
|
"/api/devices/" + encodeURIComponent(deviceId) + "/screenshot",
|
|
{ method: "POST", headers: { "X-CSRF-Token": csrfToken } }
|
|
);
|
|
if (!response.ok) {
|
|
let detail = "Screenshot failed.";
|
|
try {
|
|
const payload = await response.json();
|
|
if (payload.detail) detail = payload.detail;
|
|
} catch (_) {}
|
|
throw new Error(detail);
|
|
}
|
|
const objectUrl = URL.createObjectURL(await response.blob());
|
|
image.src = objectUrl;
|
|
image.dataset.objectUrl = objectUrl;
|
|
image.hidden = false;
|
|
status.textContent = "Captured.";
|
|
} catch (error) {
|
|
status.classList.add("error");
|
|
status.textContent = error.message || "Screenshot failed.";
|
|
} finally {
|
|
button.disabled = false;
|
|
}
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
{% endblock %}
|