diff options
author | Darius Jahandarie <djahandarie@gmail.com> | 2023-03-21 23:18:44 +0900 |
---|---|---|
committer | Darius Jahandarie <djahandarie@gmail.com> | 2023-03-23 21:52:22 +0900 |
commit | 95b2bb25d4175ff676c5a3d4e5ff0ef214f7b306 (patch) | |
tree | bcd3651e64dbf460e675db0737fa559662720f57 /test | |
parent | bb9c6ec4118c1ed8fd67ff4a760c352675086f6a (diff) |
Add visual diffing in CI
Diffstat (limited to 'test')
-rw-r--r-- | test/data/html/test-document2.html | 229 | ||||
-rw-r--r-- | test/playwright/visual.spec.js | 120 |
2 files changed, 242 insertions, 107 deletions
diff --git a/test/data/html/test-document2.html b/test/data/html/test-document2.html index fb3adb59..399c3bd8 100644 --- a/test/data/html/test-document2.html +++ b/test/data/html/test-document2.html @@ -1,34 +1,40 @@ <!DOCTYPE html> <html> - <head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width,initial-scale=1"> - <title>Yomichan Manual Tests</title> - <link rel="icon" type="image/gif" href="data:image/gif;base64,R0lGODlhEAAQAKEBAAAAAP///////////yH5BAEKAAIALAAAAAAQABAAAAImFI6Zpt0B4YkS0TCpq07xbmEgcGVRUpLaI46ZG7ppalY0jDCwUAAAOw=="> - <link rel="stylesheet" href="test-stylesheet.css"> - <script src="test-document2-script.js"></script> - </head> - <style id="container-styles"> -.container { - width: 100%; - height: 200px; - border: 1px solid #d8d8d8; - position: relative; - box-sizing: border-box; -} -.container-inner { - background-color: #f8f8f8; - padding: 0.5em; - position: absolute; - left: 0; - top: 0; - bottom: 0; - right: 0; -} -.danger { - color: #c83c28; -} - </style> + +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <title>Yomichan Manual Tests</title> + <link rel="icon" type="image/gif" + href="data:image/gif;base64,R0lGODlhEAAQAKEBAAAAAP///////////yH5BAEKAAIALAAAAAAQABAAAAImFI6Zpt0B4YkS0TCpq07xbmEgcGVRUpLaI46ZG7ppalY0jDCwUAAAOw=="> + <link rel="stylesheet" href="test-stylesheet.css"> + <script src="test-document2-script.js"></script> +</head> +<style id="container-styles"> + .container { + width: 100%; + height: 200px; + border: 1px solid #d8d8d8; + position: relative; + box-sizing: border-box; + } + + .container-inner { + background-color: #f8f8f8; + padding: 0.5em; + position: absolute; + left: 0; + top: 0; + bottom: 0; + right: 0; + } + + .danger { + color: #c83c28; + } + +</style> + <body> <h1>Yomichan Manual Tests</h1> @@ -36,56 +42,62 @@ <y-test> <y-description>Standard content.</y-description> - <div class="fullscreen-element container"><div class="container-inner"> - <div> - ありがとう - </div> - <div> - <a href="#" class="fullscreen-link">Toggle fullscreen</a> + <div class="fullscreen-element container hovertarget"> + <div class="container-inner"> + <div> + ありがとう + </div> + <div> + <a href="#" class="fullscreen-link">Toggle fullscreen</a> + </div> </div> - </div></div> + </div> </y-test> <y-test data-shadow-mode="open"> <y-description>Content inside of an open shadow DOM.</y-description> - <div class="template-content-container"></div> + <div class="template-content-container hovertarget"></div> <template> <link rel="stylesheet" href="test-stylesheet.css"> - <div class="fullscreen-element container"><div class="container-inner"> - <div> - ありがとう - </div> - <div> - <a href="#" class="fullscreen-link">Toggle fullscreen</a> + <div class="fullscreen-element container"> + <div class="container-inner"> + <div> + ありがとう + </div> + <div> + <a href="#" class="fullscreen-link">Toggle fullscreen</a> + </div> </div> - </div></div> + </div> </template> </y-test> <y-test data-shadow-mode="closed"> <y-description>Content inside of a closed shadow DOM.</y-description> - <div class="template-content-container"></div> + <div class="template-content-container hovertarget"></div> <template> <link rel="stylesheet" href="test-stylesheet.css"> - <div class="fullscreen-element container"><div class="container-inner"> - <div> - ありがとう + <div class="fullscreen-element container"> + <div class="container-inner"> + <div> + ありがとう + </div> + <div> + <a href="#" class="fullscreen-link">Toggle fullscreen</a> + </div> </div> - <div> - <a href="#" class="fullscreen-link">Toggle fullscreen</a> - </div> - </div></div> + </div> </template> </y-test> <y-test> <y-description><iframe> element.</y-description> - <iframe src="test-document2-frame1.html" allowfullscreen="true" class="container"></iframe> + <iframe src="test-document2-frame1.html" allowfullscreen="true" class="container hovertarget"></iframe> </y-test> <y-test data-shadow-mode="open"> <y-description><iframe> element inside of an open shadow DOM.</y-description> - <div class="template-content-container"></div> + <div class="template-content-container hovertarget"></div> <template> <iframe src="test-document2-frame1.html" allowfullscreen="true" class="container"></iframe> </template> @@ -93,7 +105,7 @@ <y-test data-shadow-mode="closed"> <y-description><iframe> element inside of a closed shadow DOM.</y-description> - <div class="template-content-container"></div> + <div class="template-content-container hovertarget"></div> <template> <iframe src="test-document2-frame1.html" allowfullscreen="true" class="container"></iframe> </template> @@ -101,22 +113,26 @@ <y-test> <y-description><iframe> element with data URL.</y-description> - <iframe id="iframe-with-data-url" src="data:text/html;base64,PCFET0NUWVBFIGh0bWw+DQo8aHRtbD4NCiAgICA8aGVhZD4NCiAgICAgICAgPG1ldGEgY2hhcnNldD0iVVRGLTgiPg0KICAgICAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLGluaXRpYWwtc2NhbGU9MSI+DQogICAgICAgIDx0aXRsZT5Zb21pY2hhbiBUZXN0czwvdGl0bGU+DQogICAgICAgIDxzY3JpcHQ+DQogZnVuY3Rpb24gcmVxdWVzdEZ1bGxzY3JlZW4oZWxlbWVudCkgew0KICAgIGlmIChlbGVtZW50LnJlcXVlc3RGdWxsc2NyZWVuKSB7DQogICAgICAgIGVsZW1lbnQucmVxdWVzdEZ1bGxzY3JlZW4oKTsNCiAgICB9IGVsc2UgaWYgKGVsZW1lbnQubW96UmVxdWVzdEZ1bGxTY3JlZW4pIHsNCiAgICAgICAgZWxlbWVudC5tb3pSZXF1ZXN0RnVsbFNjcmVlbigpOw0KICAgIH0gZWxzZSBpZiAoZWxlbWVudC53ZWJraXRSZXF1ZXN0RnVsbHNjcmVlbikgew0KICAgICAgICBlbGVtZW50LndlYmtpdFJlcXVlc3RGdWxsc2NyZWVuKCk7DQogICAgfSBlbHNlIGlmIChlbGVtZW50Lm1zUmVxdWVzdEZ1bGxzY3JlZW4pIHsNCiAgICAgICAgZWxlbWVudC5tc1JlcXVlc3RGdWxsc2NyZWVuKCk7DQogICAgfQ0KfQ0KDQpmdW5jdGlvbiBleGl0RnVsbHNjcmVlbigpIHsNCiAgICBpZiAoZG9jdW1lbnQuZXhpdEZ1bGxzY3JlZW4pIHsNCiAgICAgICAgZG9jdW1lbnQuZXhpdEZ1bGxzY3JlZW4oKTsNCiAgICB9IGVsc2UgaWYgKGRvY3VtZW50Lm1vekNhbmNlbEZ1bGxTY3JlZW4pIHsNCiAgICAgICAgZG9jdW1lbnQubW96Q2FuY2VsRnVsbFNjcmVlbigpOw0KICAgIH0gZWxzZSBpZiAoZG9jdW1lbnQud2Via2l0RXhpdEZ1bGxzY3JlZW4pIHsNCiAgICAgICAgZG9jdW1lbnQud2Via2l0RXhpdEZ1bGxzY3JlZW4oKTsNCiAgICB9IGVsc2UgaWYgKGRvY3VtZW50Lm1zRXhpdEZ1bGxzY3JlZW4pIHsNCiAgICAgICAgZG9jdW1lbnQubXNFeGl0RnVsbHNjcmVlbigpOw0KICAgIH0NCn0NCg0KZnVuY3Rpb24gZ2V0RnVsbHNjcmVlbkVsZW1lbnQoKSB7DQogICAgcmV0dXJuICgNCiAgICAgICAgZG9jdW1lbnQuZnVsbHNjcmVlbkVsZW1lbnQgfHwNCiAgICAgICAgZG9jdW1lbnQubXNGdWxsc2NyZWVuRWxlbWVudCB8fA0KICAgICAgICBkb2N1bWVudC5tb3pGdWxsU2NyZWVuRWxlbWVudCB8fA0KICAgICAgICBkb2N1bWVudC53ZWJraXRGdWxsc2NyZWVuRWxlbWVudCB8fA0KICAgICAgICBudWxsDQogICAgKTsNCn0NCg0KZnVuY3Rpb24gdG9nZ2xlRnVsbHNjcmVlbihlbGVtZW50KSB7DQogICAgaWYgKGdldEZ1bGxzY3JlZW5FbGVtZW50KCkpIHsNCiAgICAgICAgZXhpdEZ1bGxzY3JlZW4oKTsNCiAgICB9IGVsc2Ugew0KICAgICAgICByZXF1ZXN0RnVsbHNjcmVlbihlbGVtZW50KTsNCiAgICB9DQp9DQoNCmZ1bmN0aW9uIHNldHVwKGNvbnRhaW5lciwgZnVsbHNjcmVlbkVsZW1lbnQ9bnVsbCkgew0KICAgIGNvbnN0IGZ1bGxzY3JlZW5MaW5rID0gY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoJy5mdWxsc2NyZWVuLWxpbmsnKTsNCiAgICBpZiAoZnVsbHNjcmVlbkxpbmsgIT09IG51bGwpIHsNCiAgICAgICAgaWYgKGZ1bGxzY3JlZW5FbGVtZW50ID09PSBudWxsKSB7DQogICAgICAgICAgICBmdWxsc2NyZWVuRWxlbWVudCA9IGNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKCcuZnVsbHNjcmVlbi1lbGVtZW50Jyk7DQogICAgICAgIH0NCiAgICAgICAgZnVsbHNjcmVlbkxpbmsuYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCAoZSkgPT4gew0KICAgICAgICAgICAgdG9nZ2xlRnVsbHNjcmVlbihmdWxsc2NyZWVuRWxlbWVudCk7DQogICAgICAgICAgICBlLnByZXZlbnREZWZhdWx0KCk7DQogICAgICAgICAgICByZXR1cm4gZmFsc2U7DQogICAgICAgIH0sIGZhbHNlKTsNCiAgICB9DQoNCiAgICBjb25zdCB0ZW1wbGF0ZSA9IGNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKCd0ZW1wbGF0ZScpOw0KICAgIGNvbnN0IHRlbXBsYXRlQ29udGVudENvbnRhaW5lciA9IGNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKCcudGVtcGxhdGUtY29udGVudC1jb250YWluZXInKTsNCiAgICBpZiAodGVtcGxhdGUgIT09IG51bGwgJiYgdGVtcGxhdGVDb250ZW50Q29udGFpbmVyICE9PSBudWxsKSB7DQogICAgICAgIGNvbnN0IG1vZGUgPSBjb250YWluZXIuZGF0YXNldC5zaGFkb3dNb2RlOw0KICAgICAgICBjb25zdCBzaGFkb3cgPSB0ZW1wbGF0ZUNvbnRlbnRDb250YWluZXIuYXR0YWNoU2hhZG93KHttb2RlfSk7DQoNCiAgICAgICAgY29uc3QgY29udGFpbmVyU3R5bGVzID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcignI2NvbnRhaW5lci1zdHlsZXMnKTsNCiAgICAgICAgc2hhZG93LmFwcGVuZENoaWxkKGNvbnRhaW5lclN0eWxlcy5jbG9uZU5vZGUodHJ1ZSkpOw0KDQogICAgICAgIGNvbnN0IGNvbnRlbnQgPSBkb2N1bWVudC5pbXBvcnROb2RlKHRlbXBsYXRlLmNvbnRlbnQsIHRydWUpOw0KICAgICAgICBzZXR1cChjb250ZW50KTsNCiAgICAgICAgc2hhZG93LmFwcGVuZENoaWxkKGNvbnRlbnQpOw0KICAgIH0NCn0NCiAgICAgICAgPC9zY3JpcHQ+DQogICAgICAgIDxzdHlsZT4NCmJvZHkgew0KICAgIGZvbnQtZmFtaWx5OiAiSGVsdmV0aWNhIE5ldWUiLCBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOw0KICAgIGZvbnQtc2l6ZTogMTRweDsNCiAgICBwYWRkaW5nOiAwOw0KICAgIG1hcmdpbjogMDsNCiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjhmOGY4Ow0KfQ0KYSwgYTp2aXNpdGVkIHsNCiAgICBjb2xvcjogIzEwODBjMDsNCiAgICB0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTsNCn0NCi5jb250ZW50IHsNCiAgICBwb3NpdGlvbjogYWJzb2x1dGU7DQogICAgbGVmdDogMDsNCiAgICB0b3A6IDA7DQogICAgcmlnaHQ6IDA7DQogICAgYm90dG9tOiAwOw0KICAgIHBhZGRpbmc6IDAuNWVtOw0KICAgIGJhY2tncm91bmQtY29sb3I6ICNmOGY4Zjg7DQp9DQogICAgICAgIDwvc3R5bGU+DQogICAgPC9oZWFkPg0KPGJvZHk+PGRpdiBjbGFzcz0iY29udGVudCI+DQo8ZGl2Pg0KICAgIOOBguOCiuOBjOOBqOOBhg0KPC9kaXY+DQo8ZGl2Pg0KICAgIDxhIGhyZWY9IiMiIGNsYXNzPSJmdWxsc2NyZWVuLWxpbmsiPlRvZ2dsZSBmdWxsc2NyZWVuPC9hPg0KICAgIDxzY3JpcHQ+c2V0dXAoZG9jdW1lbnQuYm9keSwgZG9jdW1lbnQuYm9keSk7PC9zY3JpcHQ+DQo8L2Rpdj4NCjwvZGl2PjwvYm9keT4NCjwvaHRtbD4=" allowfullscreen="true" class="container"></iframe> + <iframe id="iframe-with-data-url" + src="data:text/html;base64,PCFET0NUWVBFIGh0bWw+DQo8aHRtbD4NCiAgICA8aGVhZD4NCiAgICAgICAgPG1ldGEgY2hhcnNldD0iVVRGLTgiPg0KICAgICAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLGluaXRpYWwtc2NhbGU9MSI+DQogICAgICAgIDx0aXRsZT5Zb21pY2hhbiBUZXN0czwvdGl0bGU+DQogICAgICAgIDxzY3JpcHQ+DQogZnVuY3Rpb24gcmVxdWVzdEZ1bGxzY3JlZW4oZWxlbWVudCkgew0KICAgIGlmIChlbGVtZW50LnJlcXVlc3RGdWxsc2NyZWVuKSB7DQogICAgICAgIGVsZW1lbnQucmVxdWVzdEZ1bGxzY3JlZW4oKTsNCiAgICB9IGVsc2UgaWYgKGVsZW1lbnQubW96UmVxdWVzdEZ1bGxTY3JlZW4pIHsNCiAgICAgICAgZWxlbWVudC5tb3pSZXF1ZXN0RnVsbFNjcmVlbigpOw0KICAgIH0gZWxzZSBpZiAoZWxlbWVudC53ZWJraXRSZXF1ZXN0RnVsbHNjcmVlbikgew0KICAgICAgICBlbGVtZW50LndlYmtpdFJlcXVlc3RGdWxsc2NyZWVuKCk7DQogICAgfSBlbHNlIGlmIChlbGVtZW50Lm1zUmVxdWVzdEZ1bGxzY3JlZW4pIHsNCiAgICAgICAgZWxlbWVudC5tc1JlcXVlc3RGdWxsc2NyZWVuKCk7DQogICAgfQ0KfQ0KDQpmdW5jdGlvbiBleGl0RnVsbHNjcmVlbigpIHsNCiAgICBpZiAoZG9jdW1lbnQuZXhpdEZ1bGxzY3JlZW4pIHsNCiAgICAgICAgZG9jdW1lbnQuZXhpdEZ1bGxzY3JlZW4oKTsNCiAgICB9IGVsc2UgaWYgKGRvY3VtZW50Lm1vekNhbmNlbEZ1bGxTY3JlZW4pIHsNCiAgICAgICAgZG9jdW1lbnQubW96Q2FuY2VsRnVsbFNjcmVlbigpOw0KICAgIH0gZWxzZSBpZiAoZG9jdW1lbnQud2Via2l0RXhpdEZ1bGxzY3JlZW4pIHsNCiAgICAgICAgZG9jdW1lbnQud2Via2l0RXhpdEZ1bGxzY3JlZW4oKTsNCiAgICB9IGVsc2UgaWYgKGRvY3VtZW50Lm1zRXhpdEZ1bGxzY3JlZW4pIHsNCiAgICAgICAgZG9jdW1lbnQubXNFeGl0RnVsbHNjcmVlbigpOw0KICAgIH0NCn0NCg0KZnVuY3Rpb24gZ2V0RnVsbHNjcmVlbkVsZW1lbnQoKSB7DQogICAgcmV0dXJuICgNCiAgICAgICAgZG9jdW1lbnQuZnVsbHNjcmVlbkVsZW1lbnQgfHwNCiAgICAgICAgZG9jdW1lbnQubXNGdWxsc2NyZWVuRWxlbWVudCB8fA0KICAgICAgICBkb2N1bWVudC5tb3pGdWxsU2NyZWVuRWxlbWVudCB8fA0KICAgICAgICBkb2N1bWVudC53ZWJraXRGdWxsc2NyZWVuRWxlbWVudCB8fA0KICAgICAgICBudWxsDQogICAgKTsNCn0NCg0KZnVuY3Rpb24gdG9nZ2xlRnVsbHNjcmVlbihlbGVtZW50KSB7DQogICAgaWYgKGdldEZ1bGxzY3JlZW5FbGVtZW50KCkpIHsNCiAgICAgICAgZXhpdEZ1bGxzY3JlZW4oKTsNCiAgICB9IGVsc2Ugew0KICAgICAgICByZXF1ZXN0RnVsbHNjcmVlbihlbGVtZW50KTsNCiAgICB9DQp9DQoNCmZ1bmN0aW9uIHNldHVwKGNvbnRhaW5lciwgZnVsbHNjcmVlbkVsZW1lbnQ9bnVsbCkgew0KICAgIGNvbnN0IGZ1bGxzY3JlZW5MaW5rID0gY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoJy5mdWxsc2NyZWVuLWxpbmsnKTsNCiAgICBpZiAoZnVsbHNjcmVlbkxpbmsgIT09IG51bGwpIHsNCiAgICAgICAgaWYgKGZ1bGxzY3JlZW5FbGVtZW50ID09PSBudWxsKSB7DQogICAgICAgICAgICBmdWxsc2NyZWVuRWxlbWVudCA9IGNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKCcuZnVsbHNjcmVlbi1lbGVtZW50Jyk7DQogICAgICAgIH0NCiAgICAgICAgZnVsbHNjcmVlbkxpbmsuYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCAoZSkgPT4gew0KICAgICAgICAgICAgdG9nZ2xlRnVsbHNjcmVlbihmdWxsc2NyZWVuRWxlbWVudCk7DQogICAgICAgICAgICBlLnByZXZlbnREZWZhdWx0KCk7DQogICAgICAgICAgICByZXR1cm4gZmFsc2U7DQogICAgICAgIH0sIGZhbHNlKTsNCiAgICB9DQoNCiAgICBjb25zdCB0ZW1wbGF0ZSA9IGNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKCd0ZW1wbGF0ZScpOw0KICAgIGNvbnN0IHRlbXBsYXRlQ29udGVudENvbnRhaW5lciA9IGNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKCcudGVtcGxhdGUtY29udGVudC1jb250YWluZXInKTsNCiAgICBpZiAodGVtcGxhdGUgIT09IG51bGwgJiYgdGVtcGxhdGVDb250ZW50Q29udGFpbmVyICE9PSBudWxsKSB7DQogICAgICAgIGNvbnN0IG1vZGUgPSBjb250YWluZXIuZGF0YXNldC5zaGFkb3dNb2RlOw0KICAgICAgICBjb25zdCBzaGFkb3cgPSB0ZW1wbGF0ZUNvbnRlbnRDb250YWluZXIuYXR0YWNoU2hhZG93KHttb2RlfSk7DQoNCiAgICAgICAgY29uc3QgY29udGFpbmVyU3R5bGVzID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcignI2NvbnRhaW5lci1zdHlsZXMnKTsNCiAgICAgICAgc2hhZG93LmFwcGVuZENoaWxkKGNvbnRhaW5lclN0eWxlcy5jbG9uZU5vZGUodHJ1ZSkpOw0KDQogICAgICAgIGNvbnN0IGNvbnRlbnQgPSBkb2N1bWVudC5pbXBvcnROb2RlKHRlbXBsYXRlLmNvbnRlbnQsIHRydWUpOw0KICAgICAgICBzZXR1cChjb250ZW50KTsNCiAgICAgICAgc2hhZG93LmFwcGVuZENoaWxkKGNvbnRlbnQpOw0KICAgIH0NCn0NCiAgICAgICAgPC9zY3JpcHQ+DQogICAgICAgIDxzdHlsZT4NCmJvZHkgew0KICAgIGZvbnQtZmFtaWx5OiAiSGVsdmV0aWNhIE5ldWUiLCBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOw0KICAgIGZvbnQtc2l6ZTogMTRweDsNCiAgICBwYWRkaW5nOiAwOw0KICAgIG1hcmdpbjogMDsNCiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjhmOGY4Ow0KfQ0KYSwgYTp2aXNpdGVkIHsNCiAgICBjb2xvcjogIzEwODBjMDsNCiAgICB0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTsNCn0NCi5jb250ZW50IHsNCiAgICBwb3NpdGlvbjogYWJzb2x1dGU7DQogICAgbGVmdDogMDsNCiAgICB0b3A6IDA7DQogICAgcmlnaHQ6IDA7DQogICAgYm90dG9tOiAwOw0KICAgIHBhZGRpbmc6IDAuNWVtOw0KICAgIGJhY2tncm91bmQtY29sb3I6ICNmOGY4Zjg7DQp9DQogICAgICAgIDwvc3R5bGU+DQogICAgPC9oZWFkPg0KPGJvZHk+PGRpdiBjbGFzcz0iY29udGVudCI+DQo8ZGl2Pg0KICAgIOOBguOCiuOBjOOBqOOBhg0KPC9kaXY+DQo8ZGl2Pg0KICAgIDxhIGhyZWY9IiMiIGNsYXNzPSJmdWxsc2NyZWVuLWxpbmsiPlRvZ2dsZSBmdWxsc2NyZWVuPC9hPg0KICAgIDxzY3JpcHQ+c2V0dXAoZG9jdW1lbnQuYm9keSwgZG9jdW1lbnQuYm9keSk7PC9zY3JpcHQ+DQo8L2Rpdj4NCjwvZGl2PjwvYm9keT4NCjwvaHRtbD4=" + allowfullscreen="true" class="container hovertarget"></iframe> </y-test> <y-test> <y-description><iframe> element with blob URL.</y-description> - <iframe id="iframe-with-blob-url" allowfullscreen="true" class="container"></iframe> + <iframe id="iframe-with-blob-url" allowfullscreen="true" class="container hovertarget"></iframe> </y-test> <y-test> <y-description><iframe> element with srcdoc.</y-description> - <iframe allowfullscreen="true" class="iframe-with-srcdoc container"></iframe> + <iframe allowfullscreen="true" class="iframe-with-srcdoc container hovertarget"></iframe> </y-test> <y-test> - <y-description><iframe> element with srcdoc and <code>sandbox="allow-same-origin allow-scripts"</code>.</y-description> - <iframe allowfullscreen="true" class="iframe-with-srcdoc container" sandbox="allow-same-origin allow-scripts"></iframe> + <y-description><iframe> element with srcdoc and + <code>sandbox="allow-same-origin allow-scripts"</code>.</y-description> + <iframe allowfullscreen="true" class="iframe-with-srcdoc container hovertarget" + sandbox="allow-same-origin allow-scripts"></iframe> </y-test> <y-test> @@ -124,41 +140,39 @@ <iframe> element with srcdoc and <code>sandbox="allow-scripts"</code>.<br> <span class="danger">This element is expected to not work.</span> </y-description> - <iframe allowfullscreen="true" class="iframe-with-srcdoc container" sandbox="allow-scripts"></iframe> + <iframe allowfullscreen="true" class="iframe-with-srcdoc container hovertarget" + sandbox="allow-scripts"></iframe> </y-test> <y-test> <y-description>SVG <img>.</y-description> - <img src="test-document2-frame2.svg" class="container" alt=""> + <img src="test-document2-frame2.svg" class="container hovertarget" alt=""> </y-test> <y-test> <y-description>SVG <object>.</y-description> - <object data="test-document2-frame2.svg" type="image/svg+xml" class="container"></object> + <object data="test-document2-frame2.svg" type="image/svg+xml" class="container hovertarget"></object> </y-test> <y-test> <y-description>SVG <embed>.</y-description> - <embed type="image/svg+xml" src="test-document2-frame2.svg" class="container"> + <embed type="image/svg+xml" src="test-document2-frame2.svg" class="container hovertarget"> </y-test> <y-test> <y-description>SVG <iframe>.</y-description> - <iframe src="test-document2-frame2.svg" allowfullscreen="true" class="container"></iframe> + <iframe src="test-document2-frame2.svg" allowfullscreen="true" class="container hovertarget"></iframe> </y-test> <y-test> <y-description>SVG <svg>.</y-description> - <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="container" style="background-color: #f8f8f8;" focusable="false"> - <text - x="7" - y="12" - style=" + <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="container hovertarget" + style="background-color: #f8f8f8;" focusable="false"> + <text x="7" y="12" style=" font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; fill: #000000; - dominant-baseline: hanging;" - > + dominant-baseline: hanging;"> ありがとう </text> </svg> @@ -166,42 +180,43 @@ <script> -(() => { - function stringToTypedArray(string) { - const array = new Uint8Array(string.length); - for (let i = 0; i < string.length; ++i) { - array[i] = string.charCodeAt(i); - } - return array; - } - - function dataUrlToContent(dataUrl) { - const [, type, isBase64, data] = /^data:([^;]*);(base64,)?([\w\W]*)$/.exec(dataUrl); - const content = ( - isBase64 ? - new TextDecoder().decode(stringToTypedArray(atob(data))) : - data - ); - return {content, type}; - } - - function dataUrlToBlob(dataUrl) { - const {content, type} = dataUrlToContent(dataUrl); - return new Blob([content], {type}); - } - - for (const element of document.querySelectorAll('y-test')) { - setup(element); - } - - const iframeWithDataUrl = document.querySelector('#iframe-with-data-url'); - const iframeWithBlobUrl = document.querySelector('#iframe-with-blob-url'); - iframeWithBlobUrl.src = URL.createObjectURL(dataUrlToBlob(iframeWithDataUrl.src)); - for (const iframeWithSrcdoc of document.querySelectorAll('.iframe-with-srcdoc')) { - iframeWithSrcdoc.srcdoc = dataUrlToContent(iframeWithDataUrl.src).content; - } -})(); + (() => { + function stringToTypedArray(string) { + const array = new Uint8Array(string.length); + for (let i = 0; i < string.length; ++i) { + array[i] = string.charCodeAt(i); + } + return array; + } + + function dataUrlToContent(dataUrl) { + const [, type, isBase64, data] = /^data:([^;]*);(base64,)?([\w\W]*)$/.exec(dataUrl); + const content = ( + isBase64 ? + new TextDecoder().decode(stringToTypedArray(atob(data))) : + data + ); + return { content, type }; + } + + function dataUrlToBlob(dataUrl) { + const { content, type } = dataUrlToContent(dataUrl); + return new Blob([content], { type }); + } + + for (const element of document.querySelectorAll('y-test')) { + setup(element); + } + + const iframeWithDataUrl = document.querySelector('#iframe-with-data-url'); + const iframeWithBlobUrl = document.querySelector('#iframe-with-blob-url'); + iframeWithBlobUrl.src = URL.createObjectURL(dataUrlToBlob(iframeWithDataUrl.src)); + for (const iframeWithSrcdoc of document.querySelectorAll('.iframe-with-srcdoc')) { + iframeWithSrcdoc.srcdoc = dataUrlToContent(iframeWithDataUrl.src).content; + } + })(); </script> </body> + </html> diff --git a/test/playwright/visual.spec.js b/test/playwright/visual.spec.js new file mode 100644 index 00000000..acb12e97 --- /dev/null +++ b/test/playwright/visual.spec.js @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +const path = require('path'); +const {test: base, chromium} = require('@playwright/test'); +const root = path.join(__dirname, '..', '..'); + +export const test = base.extend({ + context: async ({ }, use) => { + const pathToExtension = path.join(root, 'ext'); + const context = await chromium.launchPersistentContext('', { + // headless: false, + args: [ + '--headless=new', + `--disable-extensions-except=${pathToExtension}`, + `--load-extension=${pathToExtension}` + ] + }); + await use(context); + await context.close(); + }, + extensionId: async ({context}, use) => { + let [background] = context.serviceWorkers(); + if (!background) { + background = await context.waitForEvent('serviceworker'); + } + + const extensionId = background.url().split('/')[2]; + await use(extensionId); + } +}); +const expect = test.expect; + +test('visual', async ({context, page, extensionId}) => { + // wait for the on-install welcome.html tab to load, which becomes the foreground tab + const welcome = await context.waitForEvent('page'); + welcome.close(); // close the welcome tab so our main tab becomes the foreground tab -- otherwise, the screenshot can hang + + // open settings + await page.goto(`chrome-extension://${extensionId}/settings.html`); + + await expect(page.locator('id=dictionaries')).toBeVisible(); + + // get the locator for the disk usage indicator so we can later mask it out of the screenshot + const storage_locator = page.locator('.storage-use-finite >> xpath=..'); + + // take a simple screenshot of the settings page + await expect.soft(page).toHaveScreenshot('settings-fresh.png', {mask: [storage_locator]}); + + // load in jmdict_english.zip + await page.locator('input[id="dictionary-import-file-input"]').setInputFiles(path.join(root, 'dictionaries/jmdict_english.zip')); + await expect(page.locator('id=dictionaries')).toHaveText('Dictionaries (1 installed, 1 enabled)', {timeout: 5 * 60 * 1000}); + + // take a screenshot of the settings page with jmdict loaded + await expect.soft(page).toHaveScreenshot('settings-jmdict-loaded.png', {mask: [storage_locator]}); + + const screenshot = async (doc_number, test_number, el, offset) => { + const test_name = 'doc' + doc_number + '-test' + test_number; + + const box = await el.boundingBox(); + + // find the popup frame if it exists + let popup_frame = page.frames().find((f) => f.url().includes('popup.html')); + + // otherwise prepare for it to be attached + let frame_attached; + if (popup_frame === undefined) { + frame_attached = page.waitForEvent('frameattached'); + } + await page.mouse.move(box.x + offset.x, box.y + offset.y, {steps: 10}); // hover over the test + if (popup_frame === undefined) { + popup_frame = await frame_attached; // wait for popup to be attached + } + try { + await (await popup_frame.frameElement()).waitForElementState('visible', {timeout: 500}); // some tests don't have a popup, so don't fail if it's not there; TODO: check if the popup is expected to be there + } catch (error) { + console.log(test_name + ' has no popup'); + } + + await page.bringToFront(); // bring the page to the foreground so the screenshot doesn't hang; for some reason the frames result in page being in the background + await expect.soft(page).toHaveScreenshot(test_name + '.png'); + + await page.mouse.click(0, 0); // click away so popup disappears + await (await popup_frame.frameElement()).waitForElementState('hidden'); // wait for popup to disappear + }; + + // Load test-document1.html + await page.goto('file://' + path.join(root, 'test/data/html/test-document1.html')); + await page.setViewportSize({width: 1000, height: 1800}); + await page.keyboard.down('Shift'); + let i = 1; + for (const el of await page.locator('div > *:nth-child(1)').elementHandles()) { + await screenshot(1, i, el, {x: 6, y: 6}); + i++; + } + + // Load test-document2.html + await page.goto('file://' + path.join(root, 'test/data/html/test-document2.html')); + await page.setViewportSize({width: 1000, height: 4500}); + await page.keyboard.down('Shift'); + i = 1; + for (const el of await page.locator('.hovertarget').elementHandles()) { + await screenshot(2, i, el, {x: 15, y: 15}); + i++; + } +}); |