Skip to content

Commit

Permalink
feat: LEAP-219: Update LSF E2E with recent changes
Browse files Browse the repository at this point in the history
- HumanSignal/label-studio-frontend#1192 for Magic Wand from @BradNeuberg
- HumanSignal/label-studio-frontend#1708 with external images problem
- HumanSignal/label-studio-frontend#1021 for maxUsages missed for some reason
  • Loading branch information
hlomzik committed Mar 26, 2024
1 parent 32a70c9 commit 9098c21
Show file tree
Hide file tree
Showing 26 changed files with 6,868 additions and 3,701 deletions.
3,010 changes: 3,010 additions & 0 deletions web/libs/editor/tests/e2e/examples/data/sample-sin.json

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions web/libs/editor/tests/e2e/examples/image-keypoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const config = `
`;

const data = {
image: 'https://user.fm/files/v2-901310d5cb3fa90e0616ca10590bacb3/spacexmoon-800x501.jpg',
image: 'https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg',
};

const result = [
Expand All @@ -18,14 +18,14 @@ const result = [
from_name: 'tag',
to_name: 'img',
image_rotation: 0,
original_height: 501,
original_width: 800,
original_height: 576,
original_width: 768,
type: 'keypointlabels',
origin: 'manual',
value: {
x: 49.60000000000001,
y: 52.34042553191488,
width: 0.6471078324314267,
width: 0.6120428759942558,
keypointlabels: ['Hello'],
},
},
Expand All @@ -34,14 +34,14 @@ const result = [
from_name: 'tag',
to_name: 'img',
image_rotation: 0,
original_height: 501,
original_width: 800,
original_height: 576,
original_width: 768,
type: 'keypointlabels',
origin: 'manual',
value: {
x: 47.73333333333334,
y: 52.765957446808514,
width: 0.6666666666666666,
width: 0.6305418719211823,
keypointlabels: ['World'],
},
},
Expand Down
6 changes: 6 additions & 0 deletions web/libs/editor/tests/e2e/fragments/AtImageView.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ module.exports = {
I.waitForVisible('canvas', 5);
},

async getNaturalSize() {
const sizes = await I.executeScript(Helpers.getNaturalSize);

return sizes;
},

async getCanvasSize() {
const sizes = await I.executeScript(Helpers.getCanvasSize);

Expand Down
2 changes: 1 addition & 1 deletion web/libs/editor/tests/e2e/tests/audio/audio-errors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Scenario('Check if audio decoder error handler is showing', async function({ I,
annotations: [{ id: 'test', result: annotations }],
config,
data: {
url: '/public/files/video.mp4', // mp4 is not supported by audio decoder
url: '/files/video.mp4', // mp4 is not supported by audio decoder
},
});

Expand Down
29 changes: 27 additions & 2 deletions web/libs/editor/tests/e2e/tests/helpers.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const assert = require('assert');

/**
* Load custom example
* @param {object} params
Expand Down Expand Up @@ -157,14 +159,16 @@ const createAddEventListenerScript = (eventName, callback) => {
* Wait for the main Image object to be loaded
*/
const waitForImage = () => {
return new Promise((resolve) => {
return new Promise((resolve, reject) => {
const img = document.querySelector('[alt=LS]');

if (!img || img.complete) return resolve();
// this should be rewritten to isReady when it is ready
img.onload = () => {
setTimeout(resolve, 100);
};
// if image is not loaded in 10 seconds, reject
setTimeout(reject, 10000);
});
};

Expand Down Expand Up @@ -232,7 +236,7 @@ const convertToFixed = (data, fractionDigits = 2) => {
if (['string', 'number'].includes(typeof data)) {
const n = Number(data);

return Number.isNaN(n) ? data : Number.isInteger(n) ? n : +Number(n).toFixed(fractionDigits);
return Number.isNaN(n) ? data : Number.isInteger(n) ? n : +n.toFixed(fractionDigits);
}
if (Array.isArray(data)) {
return data.map(n => convertToFixed(n, fractionDigits));
Expand Down Expand Up @@ -531,6 +535,14 @@ async function generateImageUrl({ width, height }) {
return canvas.toDataURL();
}

const getNaturalSize = () => {
const imageObject = window.Htx.annotationStore.selected.objects.find(o => o.type === 'image');

return {
width: imageObject.naturalWidth,
height: imageObject.naturalHeight,
};
};
const getCanvasSize = () => {
const imageObject = window.Htx.annotationStore.selected.objects.find(o => o.type === 'image');

Expand Down Expand Up @@ -813,6 +825,16 @@ function hasSelectedRegion() {
return !!Htx.annotationStore.selected.highlightedNode;
}

async function doDrawingAction(I, { msg, fromX, fromY, toX, toY }) {
I.usePlaywrightTo(msg, async ({ browser, browserContext, page }) => {
await page.mouse.move(fromX, fromY);
await page.mouse.down();
await page.mouse.move(toX, toY);
await page.mouse.up();
});
I.wait(1); // Ensure that the tool is fully finished being created.
}

// `mulberry32` (simple generator with a 32-bit state)
function createRandomWithSeed(seed) {
return function() {
Expand All @@ -823,6 +845,7 @@ function createRandomWithSeed(seed) {
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}

function createRandomIntWithSeed(seed) {
const random = createRandomWithSeed(seed);

Expand Down Expand Up @@ -857,6 +880,7 @@ module.exports = {
areEqualRGB,
hasKonvaPixelColorAtPoint,
getKonvaPixelColorFromPoint,
getNaturalSize,
getCanvasSize,
getImageSize,
getImageFrameSize,
Expand All @@ -883,6 +907,7 @@ module.exports = {
omitBy,
dumpJSON,

doDrawingAction,
createRandomWithSeed,
createRandomIntWithSeed,
};
4 changes: 2 additions & 2 deletions web/libs/editor/tests/e2e/tests/image.gestures.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const createShape = {
byMultipleClicks(x, y, radius, opts = {}) {
const points = [];

for (let i = 5; i--; ) {
for (let i = 5; i--;) {
points.push([x + Math.sin(((2 * Math.PI) / 5) * i) * radius, y - Math.cos(((2 * Math.PI) / 5) * i) * radius]);
points.push([
x + (Math.sin(((2 * Math.PI) / 5) * (i - 0.5)) * radius) / 3,
Expand Down Expand Up @@ -209,7 +209,7 @@ Scenario('Creating regions by various gestures', async function({ I, AtImageView
for (const [idx, region] of Object.entries(regions)) {
I.pressKey(region.hotKey);
AtImageView[region.action](...region.params);
AtSidebar.seeRegions(+idx+1);
AtSidebar.seeRegions(+idx + 1);
}
const result = await I.executeScript(serialize);

Expand Down
20 changes: 4 additions & 16 deletions web/libs/editor/tests/e2e/tests/image.magic-wand.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const {
initLabelStudio,
doDrawingAction,
hasKonvaPixelColorAtPoint,
setKonvaLayersOpacity,
serialize,
waitForImage,
} = require('./helpers');
const assert = require('assert');

Expand Down Expand Up @@ -42,8 +42,6 @@ const annotationEmpty = {
result: [],
};

// TODO: Change these URLs to heartex URLs, and ensure the heartex bucket allows CORS access
// for these to work.
const data = {
'image': [
'http://htx-pub.s3.amazonaws.com/samples/magicwand/magic_wand_scale_1_20200902_015806_26_2235_1B_AnalyticMS_00750_00750.jpg',
Expand All @@ -54,16 +52,6 @@ const data = {
'thumb': 'http://htx-pub.s3.amazonaws.com/samples/magicwand/magic_wand_thumbnail_20200902_015806_26_2235_1B_AnalyticMS_00750_00750.jpg',
};

async function magicWand(I, { msg, fromX, fromY, toX, toY }) {
I.usePlaywrightTo(msg, async ({ page }) => {
await page.mouse.move(fromX, fromY);
await page.mouse.down();
await page.mouse.move(toX, toY);
await page.mouse.up();
});
I.wait(1); // Ensure that the magic wand brush region is fully finished being created.
}

async function assertMagicWandPixel(I, x, y, assertValue, rgbArray, msg) {
const hasPixel = await I.executeScript(hasKonvaPixelColorAtPoint, [x, y, rgbArray, 1]);

Expand Down Expand Up @@ -101,8 +89,8 @@ Scenario('Make sure the magic wand works in a variety of scenarios', async funct
AtSidebar.seeRegions(0);

I.say('Magic wanding clouds with cloud class in upper left of image');
await magicWand(I, { msg: 'Fill in clouds upper left', fromX: 258, fromY: 214, toX: 650, toY: 650 });
await magicWand(I, { msg: 'Fill in clouds lower left', fromX: 337, fromY: 777, toX: 650, toY: 650 });
await doDrawingAction(I, { msg: 'Fill in clouds upper left', fromX: 258, fromY: 214, toX: 650, toY: 650 });
await doDrawingAction(I, { msg: 'Fill in clouds lower left', fromX: 337, fromY: 777, toX: 650, toY: 650 });

I.say('Ensuring repeated magic wands back to back with same class collapsed into single region');
AtSidebar.seeRegions(1);
Expand Down Expand Up @@ -173,7 +161,7 @@ Scenario('Make sure the magic wand works in a variety of scenarios', async funct
I.pressKey('2');

I.say('Magic wanding cloud shadows with cloud shadow class in center of zoomed image');
await magicWand(I, { msg: 'Cloud shadow in middle of image', fromX: 390, fromY: 500, toX: 500, toY: 500 });
await doDrawingAction(I, { msg: 'Cloud shadow in middle of image', fromX: 390, fromY: 500, toX: 500, toY: 500 });

I.say('Ensuring new cloud shadow magic wand region gets added to sidebar');
AtSidebar.seeRegions(2);
Expand Down
110 changes: 110 additions & 0 deletions web/libs/editor/tests/e2e/tests/image.selected-region.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/* global Feature, Scenario */

const {
doDrawingAction,
initLabelStudio,
waitForImage,
} = require('./helpers');
const assert = require('assert');

Feature('Test Image Region Stay Selected Between Tools');

const PLANET = {
color: '#00FF00',
rgbArray: [0, 255, 0],
};
const MOONWALKER = {
color: '#0000FF',
rgbArray: [0, 0, 255],
};

const config = `
<View>
<Image name="image" value="$image" crossOrigin="anonymous" />
<Brush name="brush" toName="image" />
<MagicWand name="magicwand" toName="image" />
<Labels name="labels" toName="image" fillOpacity="0.5" strokeWidth="5">
<Label value="Planet" background="${PLANET.color}"></Label>
<Label value="Moonwalker" background="${MOONWALKER.color}"></Label>
</Labels>
</View>`;

const annotationEmpty = {
id: '1000',
result: [],
};

const data = {
image:
'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg',
};

async function testRegion(testType, toolAccelerator, I, LabelStudio, AtImageView, AtSidebar) {
const params = {
config,
data,
annotations: [annotationEmpty],
};

LabelStudio.setFeatureFlags({
'fflag_feat_front_dev_4081_magic_wand_tool': true,
});

I.amOnPage('/');

I.executeScript(initLabelStudio, params);

AtImageView.waitForImage();
await AtImageView.lookForStage();
I.executeScript(waitForImage);

I.say(`Select ${testType} & planet class`);
I.pressKey(toolAccelerator);
I.pressKey('1');

I.say('There should be no regions initially');
AtSidebar.seeRegions(0);

I.say(`${testType} initial region`);
await doDrawingAction(I, { msg: `Initial ${testType}`, fromX: 150, fromY: 110, toX: 150+50, toY: 110+50 });

I.say('There should now be a single region');
AtSidebar.seeRegions(1);

I.say(`Using Eraser on ${testType} region`);
I.pressKey('E');
I.usePlaywrightTo('Erasing', async ({ browser, browserContext, page }) => {
await page.mouse.move(150, 150);
await page.mouse.down();
await page.mouse.move(150+100, 150);
await page.mouse.up();
});

I.say(`Doing another ${testType} with same class after erasing`);
I.pressKey(toolAccelerator);
await doDrawingAction(I, { msg: `${testType} after erasing`, fromX: 280, fromY: 480, toX: 280+50, toY: 480+50 });

I.say('There should still only be one region');
AtSidebar.seeRegions(1);

I.say('Zooming and selecting pan tool');
I.click('button[aria-label="zoom-in"]');
I.click('button[aria-label="zoom-in"]');
I.pressKey('H');

I.say(`Doing another ${testType} after zooming and selecting pan tool`);
I.pressKey(toolAccelerator);
await doDrawingAction(I, { msg: `${testType} after zoom and pan selected`,
fromX: 400, fromY: 200, toX: 400+15, toY: 400+15 });

I.say('There should still only be one region');
AtSidebar.seeRegions(1);
}

Scenario('Selected brush region should stay between tools', async function({ I, LabelStudio, AtImageView, AtSidebar }) {
await testRegion('brush', 'B', I, LabelStudio, AtImageView, AtSidebar);
});

Scenario('Selected Magic Wand region should stay between tools', async function({ I, LabelStudio, AtImageView, AtSidebar }) {
await testRegion('magicwand', 'W', I, LabelStudio, AtImageView, AtSidebar);
});
4 changes: 2 additions & 2 deletions web/libs/editor/tests/e2e/tests/image.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,10 @@ Scenario('Image with perRegion tags', async function({ I, AtImageView, AtSidebar
assert.deepStrictEqual(result[0].value.rectanglelabels, ['Moonwalker']);
});

const outOfBoundsFFs = new DataTable(['FF_DEV_3793'])
const outOfBoundsFFs = new DataTable(['FF_DEV_3793']);

outOfBoundsFFs.add([true]);
outOfBoundsFFs.add([false])
outOfBoundsFFs.add([false]);

Data(outOfBoundsFFs)
.Scenario('Can\'t create rectangles outside of canvas', async ({
Expand Down
Loading

0 comments on commit 9098c21

Please sign in to comment.