Skip to content

Commit

Permalink
Tiered tests (#629)
Browse files Browse the repository at this point in the history
* Refactor HelioviewerInterface to support mobile and use tags

* Add "implements" to page objects

* rename NormalView to DesktopView

* run mobile tests in desktop folder

* Update jump_with_time_range to run on mobile

* Fix mobile set obs time

* prettier

* update mobile selector

* typo

* Update test setting obstime on mobile

* Fix missing tile bug

* Fix typo

* prettier

* remove console.log

* Change screenshot comparison method

* fix ts error

* make snapshot dir

* magic

* Add jump_label to screenshot for parallel tests
  • Loading branch information
dgarciabriseno authored Oct 30, 2024
1 parent 75c731e commit 401d733
Show file tree
Hide file tree
Showing 10 changed files with 495 additions and 240 deletions.
10 changes: 9 additions & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import { defineConfig, devices } from '@playwright/test';
* Test files must be prefixed with 'mobile_', 'desktop_' or 'all_'
*/
const Platforms = {
mobile: /(mobile)/,
mobile: /(mobile|(desktop.*@Mobile))/,
desktop: /(desktop)/,
mobileTag: /@Mobile/,
desktopTag: /@Desktop/
}

/**
Expand Down Expand Up @@ -55,36 +57,42 @@ export default defineConfig({
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
grep: Platforms.desktop,
grepInvert: Platforms.mobileTag
},

{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
grep: Platforms.desktop,
grepInvert: Platforms.mobileTag
},

{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
grep: Platforms.desktop,
grepInvert: Platforms.mobileTag
},

{
name: 'edge',
use: { ...devices['Desktop Edge'] },
grep: Platforms.desktop,
grepInvert: Platforms.mobileTag
},

/* Test against mobile viewports. */
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
grep: Platforms.mobile,
grepInvert: Platforms.desktopTag
},
{
name: 'Mobile Safari',
use: { ...devices['iPhone 12'] },
grep: Platforms.mobile,
grepInvert: Platforms.desktopTag
},
],

Expand Down
32 changes: 25 additions & 7 deletions resources/js/Tiling/Layer/TileLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ var TileLayer = Layer.extend(
this.baseDiffTime = (typeof baseDiffTime == 'undefined' ? $('#date').val()+' '+$('#time').val() : baseDiffTime);
this.name = name;
this.tileVisibilityRange = {xStart: 0, xEnd: 0, yStart: 0, yEnd: 0};
this.tileset = 0;
// Mapping of x, y coordinates to HTML img tags
this._tiles = {}
},
Expand Down Expand Up @@ -360,18 +361,27 @@ var TileLayer = Layer.extend(
this.sharpen = !this.sharpen;
},

_updateTileset: function (tileset) {
if (tileset > this.tileset) {
this.tileset = tileset;
}
},

/**
* @description Generates URL to retrieve a single Tile and displays the transparent tile if request fails
* @param {Int} x Tile X-coordinate
* @param {Int} y Tile Y-coordinate
* @param {Int} tileset Number representing the current set of tiles
* @param {Function} onTileLoadComplete -- callback function to this.tileLoader.onTileLoadComplete
* @returns {String} URL to retrieve the requested tile
*
* IE: CSS opacities do not behave properly with absolutely positioned elements. Opacity is therefor
* set at tile-level.
*/
getTile: function (event, x, y, onTileLoadComplete) {
getTile: function (event, x, y, tileset, onTileLoadComplete) {
var top, left, ts, img, rf, emptyTile;
// Track the latest tileset
this._updateTileset(tileset);

left = x * this.tileSize;
top = y * this.tileSize;
Expand All @@ -389,24 +399,32 @@ var TileLayer = Layer.extend(
img.css("opacity", this.opacity / 100);
}

const renderTile = () => {
if (tileset >= this.tileset) {
this._replaceTile(x, y, img);
if (onTileLoadComplete) {
onTileLoadComplete();
}
} else {
// remove stale tile.
$(img).remove();
}
}

// Load tile
let layer = this;
img.on('error', function (e) {
layer._replaceTile(x, y, this);
renderTile();
img.unbind("error");
$(this).attr("src", emptyTile);
}).on('load', function () {
layer._replaceTile(x, y, this);
renderTile();
$(this).width(512).height(512); // Wait until image is done loading specify dimensions in order to prevent
// Firefox from displaying place-holders
}).attr("src", this.getTileURL(x, y));
$(img).width(0).height(0);

// Makes sure all of the images have finished downloading before swapping them in
img.appendTo(this.domNode);
if (onTileLoadComplete) {
img.on('load', onTileLoadComplete);
}
},

/**
Expand Down
16 changes: 14 additions & 2 deletions resources/js/Tiling/Layer/TileLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ var TileLoader = Class.extend(
this.loadedTiles = {};
this.width = 0;
this.height = 0;
/**
* This tileset ID is a unique ID which is incremented each time
* all current tiles are reloaded. This happens when the user changes
* the current data source, or when the user zooms in and out. It is
* used to deal with a race condition where tiles from an older tileset
* may finish loading after the newer tiles are loaded. We use this
* tileset ID to choose which tile should be displayed in the event
* of this race condition.
*/
this.tileSetId = 1;
this.tileVisibilityRange = tileVisibilityRange;
},

Expand Down Expand Up @@ -106,7 +116,7 @@ var TileLoader = Class.extend(
}
if (!this.loadedTiles[i][j] && this.validTiles[i][j]) {
this.loadedTiles[i][j] = true;
$(this.domNode).trigger('get-tile', [i, j]);
$(this.domNode).trigger('get-tile', [i, j, this.tileSetId]);
}
}
}
Expand Down Expand Up @@ -144,6 +154,7 @@ var TileLoader = Class.extend(
* @param {Boolean} removeOldTilesFirst Whether old tiles should be removed before or after new ones are loaded.
*/
reloadTiles: function (removeOldTilesFirst) {
this.tileSetId += 1;
this.removeOldTilesFirst = removeOldTilesFirst;
this.numTilesLoaded = 0;
this.loadedTiles = {};
Expand All @@ -161,7 +172,7 @@ var TileLoader = Class.extend(
this._iterateVisibilityRange(this.tileVisibilityRange, (i, j) => {
if (this.validTiles[i] && this.validTiles[i][j]) {
this.numTiles += 1;
$(this.domNode).trigger('get-tile', [i, j, $.proxy(this.onTileLoadComplete, this)]);
$(this.domNode).trigger('get-tile', [i, j, this.tileSetId, $.proxy(this.onTileLoadComplete, this)]);

if (!this.loadedTiles[i]) {
this.loadedTiles[i] = {};
Expand Down Expand Up @@ -193,6 +204,7 @@ var TileLoader = Class.extend(
},

onTileLoadComplete: function () {
// Only count tiles matching the latest tileset
this.numTilesLoaded += 1;

// After all tiles have loaded, stop indicator (and remove old-tiles if haven't already)
Expand Down
62 changes: 32 additions & 30 deletions tests/desktop/multi/tiles.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { test, expect } from "@playwright/test";
import { HelioviewerInterfaceFactory, HelioviewerViews } from "../../page_objects/helioviewer_interface";
import { EmbedView, HelioviewerFactory, MinimalView, DesktopView } from "../../page_objects/helioviewer_interface";

HelioviewerViews.forEach((view) => {
[EmbedView, MinimalView, DesktopView].forEach((view) => {
/**
* A recurring issue in Helioviewer deals with computing which tiles should
* be displayed in the viewport based on the screen size, zoom amount, and
Expand All @@ -17,32 +17,34 @@ HelioviewerViews.forEach((view) => {
* This test verifies that the black space does NOT remain, and that the tile does get loaded
* when it is dragged into the viewport.
*/
test(`[${view}] Verify image tiles are loaded when the viewport pans to tile boundaries after zooming in and out`, async ({
page
}) => {
let hv = HelioviewerInterfaceFactory.Create(view, page);
await hv.Load("/");
await hv.CloseAllNotifications();
// Zoom in to increase the number of tiles.
await hv.ZoomIn(4);
// Zoom out, to test the zoom out
await hv.ZoomOut(1);
// Tiles in column x=1 should be visible from y range y=-2 to y=1
await expect(page.locator("//img[contains(@src, 'x=1&y=-2')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=1&y=-1')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=1&y=0')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=1&y=1')]")).toHaveCount(1);
// Same for tiles in column x=2
await expect(page.locator("//img[contains(@src, 'x=2&y=-2')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=2&y=-1')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=2&y=0')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=2&y=1')]")).toHaveCount(1);
// Tiles in row y=1 should be visible from x range x=-3 to x=2
await expect(page.locator("//img[contains(@src, 'x=-3&y=1')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=-2&y=1')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=-1&y=1')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=0&y=1')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=1&y=1')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=2&y=1')]")).toHaveCount(1);
});
test(
`[${view.name}] Verify image tiles are loaded when the viewport pans to tile boundaries after zooming in and out`,
{ tag: view.tag },
async ({ page }, info) => {
let hv = HelioviewerFactory.Create(view, page, info);
await hv.Load("/");
await hv.CloseAllNotifications();
// Zoom in to increase the number of tiles.
await hv.ZoomIn(4);
// Zoom out, to test the zoom out
await hv.ZoomOut(1);
// Tiles in column x=1 should be visible from y range y=-2 to y=1
await expect(page.locator("//img[contains(@src, 'x=1&y=-2')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=1&y=-1')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=1&y=0')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=1&y=1')]")).toHaveCount(1);
// Same for tiles in column x=2
await expect(page.locator("//img[contains(@src, 'x=2&y=-2')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=2&y=-1')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=2&y=0')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=2&y=1')]")).toHaveCount(1);
// Tiles in row y=1 should be visible from x range x=-3 to x=2
await expect(page.locator("//img[contains(@src, 'x=-3&y=1')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=-2&y=1')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=-1&y=1')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=0&y=1')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=1&y=1')]")).toHaveCount(1);
await expect(page.locator("//img[contains(@src, 'x=2&y=1')]")).toHaveCount(1);
}
);
});
Loading

0 comments on commit 401d733

Please sign in to comment.