const Helpers = require("./helpers"); const Asserts = require("../utils/asserts"); const assert = require("assert"); Feature("Empty labels"); const { examples, Utils } = require("../examples/"); function isLabelType(type) { return type.toLowerCase().endsWith("labels"); } function isLabels(val, key) { return isLabelType(key); } examples.forEach((example) => { const { annotations, config, data, result = annotations[0].result, title } = example; Scenario(`Different from_name -> ${title}`, async ({ I, LabelStudio, AtOutliner, AtAudioView }) => { let { result = annotations[0].result } = example; LabelStudio.setFeatureFlags({ ff_front_dev_2715_audio_3_280722_short: true, }); result = result.filter((res) => isLabelType(res.type)); const params = { annotations: [{ id: "test", result }], data }; const configTree = Utils.parseXml(config); Utils.xmlForEachNode(configTree, (node) => { if (node["#name"].toLowerCase().endsWith("labels") && node.$) { node.$.name = `Not-${node.$.name}`; } }); params.config = Utils.renderXml(configTree); const regionsCount = Utils.countRegionsInResult(result); I.amOnPage("/"); LabelStudio.init(params); LabelStudio.waitForObjectsReady(); AtOutliner.seeRegions(regionsCount); if (Utils.xmlFindBy(configTree, (node) => node["#name"] === "Audio")) { await AtAudioView.waitForAudio(); } if (regionsCount) { const restored = await LabelStudio.serialize(); Asserts.notDeepEqualWithTolerance(result, restored, 1); for (let i = result.length; i--; ) { Asserts.deepEqualWithTolerance( Helpers.omitBy(result[i], (val, key) => key === "from_name" || isLabels(val, key)), Helpers.omitBy(restored[i], (val, key) => key === "from_name" || isLabels(val, key)), 1, ); } } }); // For classifications that scenario does not make sense if (title.includes("Classifications,")) return; Scenario(`Nonexistent from_name -> ${title}`, async ({ I, LabelStudio, AtTopbar, AtOutliner }) => { const params = { annotations: [{ id: "test", result }], data }; const configTree = Utils.parseXml(config); Utils.xmlFilterNodes(configTree, (node) => { return !node["#name"].toLowerCase().endsWith("labels"); }); params.config = Utils.renderXml(configTree); const regionsCount = Utils.countRegionsInResult(result); I.amOnPage("/"); LabelStudio.init(params); AtTopbar.see("Update"); AtOutliner.dontSeeRegions(regionsCount); AtOutliner.dontSeeRegions(); }); }); const SINGLE_TYPE = "single"; const MULTIPLE_TYPE = "multiple"; [SINGLE_TYPE, MULTIPLE_TYPE].forEach((type) => { Scenario(`Making labels empty -> choice="${type}"`, async ({ I, LabelStudio, AtOutliner, AtAudioView, AtLabels }) => { async function expectSelectedLabels(expectedNum) { const selectedLabelsNum = await I.grabNumberOfVisibleElements(AtLabels.locateSelected()); assert.strictEqual(selectedLabelsNum, expectedNum); } async function clickLabelWithLengthExpection(labelNumber, expectedLength, expectSelectedNum) { AtLabels.clickLabel(`${labelNumber}`); const restored = await LabelStudio.serialize(); assert.strictEqual(restored[0].value.labels.length, expectedLength); await expectSelectedLabels(expectSelectedNum); I.pressKey(["u"]); await expectSelectedLabels(0); AtOutliner.clickRegion(1); await expectSelectedLabels(expectSelectedNum); } async function clickLabelWithSelectedExpection(labelNumber, expectSelectedNum) { AtLabels.clickLabel(`${labelNumber}`); await expectSelectedLabels(expectSelectedNum); } const audioRegions = require("../examples/audio-regions"); const { annotations, config, data } = audioRegions; let { result = annotations[0].result } = require("../examples/audio-regions"); result = result.filter((r) => isLabelType(r.type)).filter((r, idx) => !idx); const params = { annotations: [{ id: "test", result }], data, config }; const configTree = Utils.parseXml(config); Utils.xmlForEachNode(configTree, (node) => { if (node["#name"].toLowerCase().endsWith("labels") && node.$) { node.$.allowempty = true; node.$.choice = type; } }); params.config = Utils.renderXml(configTree); const regionsCount = Utils.countRegionsInResult(result); I.amOnPage("/"); LabelStudio.init(params); await AtAudioView.waitForAudio(); AtOutliner.seeRegions(regionsCount); AtOutliner.clickRegion(1); AtLabels.clickLabel("1"); const restored = await LabelStudio.serialize(); Asserts.notDeepEqualWithTolerance(result[0], restored[0], 1); Asserts.deepEqualWithTolerance(Helpers.omitBy(result[0], isLabels), Helpers.omitBy(restored[0], isLabels), 1); assert.strictEqual(restored[0].value.labels.length, 0); await clickLabelWithLengthExpection(2, 1, 1); switch (type) { case SINGLE_TYPE: await clickLabelWithLengthExpection(3, 1, 1); break; case MULTIPLE_TYPE: await clickLabelWithLengthExpection(3, 2, 2); break; } await clickLabelWithLengthExpection(1, 0, 1); await clickLabelWithLengthExpection(2, 1, 1); await clickLabelWithLengthExpection(2, 0, 1); await clickLabelWithLengthExpection(1, 0, 1); I.pressKey(["u"]); await clickLabelWithSelectedExpection(3, 1); switch (type) { case SINGLE_TYPE: await clickLabelWithSelectedExpection(2, 1); break; case MULTIPLE_TYPE: await clickLabelWithSelectedExpection(2, 2); break; } await clickLabelWithSelectedExpection(1, 1); await clickLabelWithSelectedExpection(2, 1); await clickLabelWithSelectedExpection(2, 0); await clickLabelWithSelectedExpection(1, 1); await clickLabelWithSelectedExpection(1, 0); }); }); Scenario("Consistency of empty labels", async ({ I, LabelStudio, AtOutliner, AtImageView, AtLabels, AtPanels }) => { const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS); const { config, data } = require("../examples/image-bboxes"); const params = { annotations: [{ id: "test", result: [] }], data }; const configTree = Utils.parseXml(config); Utils.xmlForEachNode(configTree, (node) => { if (node["#name"].toLowerCase().endsWith("labels") && node.$) { node.$.allowempty = true; } }); params.config = Utils.renderXml(configTree); I.amOnPage("/"); LabelStudio.init(params); AtDetailsPanel.collapsePanel(); AtOutliner.seeRegions(0); LabelStudio.waitForObjectsReady(); AtLabels.clickLabel("1"); AtImageView.dragKonva(200, 200, 100, 100); const shapesNum = await AtImageView.countKonvaShapes(); assert.strictEqual(shapesNum, 1); const restored = await LabelStudio.serialize(); const canvasSize = await AtImageView.getCanvasSize(); const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height); Asserts.deepEqualWithTolerance( restored[0].value, convertToImageSize({ x: 200, y: 200, width: 100, height: 100, rotation: 0, rectanglelabels: [] }), 1, ); });