(cdp, udid)
| 1040 | } |
| 1041 | |
| 1042 | async function collectVisualArtifactSample(cdp, udid) { |
| 1043 | const referenceDataUrl = await fetchReferenceScreenshotDataUrl(udid); |
| 1044 | const screenshotPath = `${apiRootPath}/api/simulators/${encodeURIComponent( |
| 1045 | udid, |
| 1046 | )}/screenshot.png`; |
| 1047 | return evaluate( |
| 1048 | cdp, |
| 1049 | ` |
| 1050 | (async () => { |
| 1051 | const video = document.querySelector("video.stream-video"); |
| 1052 | if (!video || video.readyState < 2 || !video.videoWidth || !video.videoHeight) { |
| 1053 | throw new Error("live video is not ready for visual comparison"); |
| 1054 | } |
| 1055 | |
| 1056 | const source = await loadReferenceScreenshot(); |
| 1057 | const width = Math.min(240, video.videoWidth, source.width); |
| 1058 | const height = Math.max(1, Math.round(width * (video.videoHeight / video.videoWidth))); |
| 1059 | const canvas = document.createElement("canvas"); |
| 1060 | canvas.width = width; |
| 1061 | canvas.height = height; |
| 1062 | const videoCanvas = document.createElement("canvas"); |
| 1063 | videoCanvas.width = width; |
| 1064 | videoCanvas.height = height; |
| 1065 | const sourceContext = canvas.getContext("2d", { willReadFrequently: true }); |
| 1066 | const videoContext = videoCanvas.getContext("2d", { willReadFrequently: true }); |
| 1067 | sourceContext.imageSmoothingEnabled = true; |
| 1068 | videoContext.imageSmoothingEnabled = true; |
| 1069 | sourceContext.drawImage(source, 0, 0, width, height); |
| 1070 | videoContext.drawImage(video, 0, 0, width, height); |
| 1071 | |
| 1072 | const sourceData = sourceContext.getImageData(0, 0, width, height).data; |
| 1073 | const videoData = videoContext.getImageData(0, 0, width, height).data; |
| 1074 | const tileSize = 16; |
| 1075 | const tileColumns = Math.ceil(width / tileSize); |
| 1076 | const tileRows = Math.ceil(height / tileSize); |
| 1077 | const tileSums = new Float64Array(tileColumns * tileRows); |
| 1078 | const tileCounts = new Uint32Array(tileColumns * tileRows); |
| 1079 | let badPixels = 0; |
| 1080 | let sum = 0; |
| 1081 | let maxPixelDiff = 0; |
| 1082 | for (let y = 0; y < height; y += 1) { |
| 1083 | for (let x = 0; x < width; x += 1) { |
| 1084 | const offset = (y * width + x) * 4; |
| 1085 | const diff = ( |
| 1086 | Math.abs(sourceData[offset] - videoData[offset]) + |
| 1087 | Math.abs(sourceData[offset + 1] - videoData[offset + 1]) + |
| 1088 | Math.abs(sourceData[offset + 2] - videoData[offset + 2]) |
| 1089 | ) / 3; |
| 1090 | sum += diff; |
| 1091 | maxPixelDiff = Math.max(maxPixelDiff, diff); |
| 1092 | if (diff > 48) { |
| 1093 | badPixels += 1; |
| 1094 | } |
| 1095 | const tileIndex = Math.floor(y / tileSize) * tileColumns + Math.floor(x / tileSize); |
| 1096 | tileSums[tileIndex] += diff; |
| 1097 | tileCounts[tileIndex] += 1; |
| 1098 | } |
| 1099 | } |
no test coverage detected