Added Cypress testing for feature: Multiple tasks creating from videos (#5028)

* add tests for try of creating multitasks with images and video

* add tests seccess creating video with Remote source

* reduce time of debounce

* try fix 107, 118 tests

* try fix 107 tests

* fix test with attach video

* disable test with attach video

* fix url to remote videos

* Aborted extra changes

* Updated files

* Refactored case 107

* Fixed almost all tests

* Aborted extra changes

* removed extra files

* Improved tests, added tests for local videos

* Fixed corner case

* Fix ://

* Redesigned a way of getting a file type

* Added html type to button

* Some refactoring

* Removed extra file

* sdk tests: fix path for file share images

Co-authored-by: Boris <sekachev.bs@gmail.com>
Co-authored-by: kirill-sizov <sizow.k.d@gmail.com>
main
Aleksey Alekseev 3 years ago committed by GitHub
parent e723116e56
commit c3dd3499e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -19,12 +19,7 @@ import { getCore, Storage } from 'cvat-core-wrapper';
import ConnectedFileManager from 'containers/file-manager/file-manager';
import LabelsEditor from 'components/labels-editor/labels-editor';
import { Files } from 'components/file-manager/file-manager';
import {
getFileContentType,
getContentTypeRemoteFile,
getFileNameFromPath,
} from 'utils/files';
import { getFileContentType, getContentTypeRemoteFile, getFileNameFromPath } from 'utils/files';
import BasicConfigurationForm, { BaseConfiguration } from './basic-configuration-form';
import ProjectSearchField from './project-search-field';
@ -268,7 +263,7 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
}
};
private handleUploadRemoteFiles = async (urls: string[]): Promise<void> => {
private handleUploadRemoteFiles = (urls: string[]): void => {
const { many } = this.props;
const { files } = this.state;
@ -280,7 +275,7 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
if (!many && length > 1) {
let index = 0;
while (index < length) {
const isImageFile = await getContentTypeRemoteFile(urls[index]) === 'image';
const isImageFile = getContentTypeRemoteFile(urls[index]) === 'image';
if (!isImageFile) {
uploadFileErrorMessage = UploadFileErrorMessages.one;
break;
@ -290,7 +285,7 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
} else if (many) {
let index = 0;
while (index < length) {
const isVideoFile = await getContentTypeRemoteFile(urls[index]) === 'video';
const isVideoFile = getContentTypeRemoteFile(urls[index]) === 'video';
if (!isVideoFile) {
uploadFileErrorMessage = UploadFileErrorMessages.multi;
break;
@ -884,7 +879,12 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
return (
<Row justify='end' gutter={5}>
<Col>
<Button type='primary' onClick={this.handleSubmitMutliTasks} disabled={!!uploadFileErrorMessage}>
<Button
htmlType='submit'
type='primary'
onClick={this.handleSubmitMutliTasks}
disabled={!!uploadFileErrorMessage}
>
Submit&nbsp;
{currentFiles.length}
&nbsp;tasks

@ -64,13 +64,13 @@ export default function MultiTasksProgress(props: Props): JSX.Element {
message={(
<div>
{percent === 100 ? (
<Row>
<Row className='cvat-create-multi-tasks-state'>
<Col>
Finished
</Col>
</Row>
) : null}
<Row>
<Row className='cvat-create-multi-tasks-progress'>
<Col>
{`Pending: ${countPending} `}
</Col>

@ -16,89 +16,40 @@ export function checkFileTypesEqual(files: File[]): boolean {
return files.every((file) => getFileContentType(file) === typeFirstFile);
}
function checkCreatingVideoElement(url: string): Promise<void> {
return new Promise((resolve, reject) => {
const el = document.createElement('video');
el.src = url;
el.onloadedmetadata = () => {
el.remove();
resolve();
};
el.onerror = reject;
});
}
function checkCreatingImageElement(url: string): Promise<void> {
return new Promise((resolve, reject) => {
const el = document.createElement('img');
el.src = url;
el.onload = () => {
el.remove();
resolve();
};
el.onerror = reject;
});
}
function getUrlExtension(url: string): string {
return url.split(/[#?]/)[0].split('.').pop()?.trim() || '';
return (url.split(/[#?]/)[0].split('.').pop()?.trim() || '').toLowerCase();
}
// source https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types
const IMAGE_EXTENSIONS = ['apng', 'avif', 'gif', 'jpg', 'jpeg', 'jfif', 'pjpeg', 'pjp', 'png', 'webp'];
const IMAGE_EXTENSIONS = ['3ds', 'ag', 'arw', 'bay', 'bmp', 'bmq', 'cgm', 'cr2', 'crw', 'cs1', 'cs2', 'cur', 'dcr',
'dds', 'djv', 'djvu', 'dng', 'dwg', 'dxf', 'emf', 'eps', 'eps.bz2', 'eps.gz', 'epsf', 'epsf.bz2', 'epsf.gz',
'epsi', 'epsi.bz2', 'epsi.gz', 'erf', 'exr', 'fff', 'fig', 'fits', 'g3', 'gif', 'hdr', 'hrd', 'icb',
'icns', 'ico', 'ief', 'iff', 'ilbm', 'jng', 'jp2', 'jpe', 'jpeg', 'jpf', 'jpg', 'jpx', 'k25', 'kdc',
'lbm', 'lwo', 'lwob', 'lws', 'mdc', 'mdi', 'mos', 'mrw.', 'msod', 'nef', 'ora', 'orf', 'pbm', 'pct', 'pcx',
'pef', 'pgm', 'pic', 'pict', 'pict1', 'pict2', 'png', 'pnm', 'pntg', 'pnx', 'ppm', 'psd', 'qif', 'qtif', 'raf',
'ras', 'raw', 'rdc', 'rgb', 'rle', 'rp', 'rw2', 'sgi', 'sk', 'sk1', 'sr2', 'srf', 'sun', 'svg', 'svgz', 'tga',
'tif', 'tiff', 'tpic', 'vda', 'vst', 'wbmp', 'webp', 'wmf', 'x3f', 'xbm', 'xcf', 'xcf.bz2', 'xcf.gz', 'xpm', 'xwd',
];
// source https://en.wikipedia.org/wiki/Video_file_format
const VIDEO_EXTENSIONS = ['webm', 'mkv', 'flv', 'flv', 'vob', 'ogv', 'ogg', 'drc', 'gifv', 'mng', 'avi', 'MTS',
'M2TS', 'TS', 'mov', 'qt', 'wmv', 'yuv', 'rm', 'rmvb', 'viv', 'asf', 'amv', 'mp4', 'm4p', 'm4v',
'mpg', 'mp2', 'mpeg', 'mpe', 'mpv', 'mpg', 'mpeg', 'm2v', 'm4v', 'svi', '3gp', '3g2', 'mxf', 'roq',
'nsv', 'flv', 'f4v', 'f4p', 'f4a', 'f4b',
const VIDEO_EXTENSIONS = ['3g2', '3ga', '3gp', '3gp2', '3gpp', '3gpp2', 'amv', 'asf', 'avf', 'avi', 'axv', 'bdm',
'bdmv', 'clpi', 'cpi', 'divx', 'drc', 'dv', 'f4a', 'f4b', 'f4p', 'f4v', 'flc', 'fli', 'flv', 'fxm', 'gifv',
'lrv', 'm1u', 'm2t', 'm2ts', 'm2v', 'm4p', 'm4u', 'm4v', 'mk3d', 'mkv', 'mng', 'moov', 'mov', 'movie',
'mp2', 'mp4', 'mpe', 'mpeg', 'mpg', 'mpl', 'mpls', 'mpv', 'mts', 'mxf', 'mxu', 'nsv', 'ogg', 'ogm', 'ogv',
'qt', 'qtvr', 'rm', 'rmvb', 'roq', 'rv', 'rvx', 'svi', 'ts', 'vdr', 'viv', 'vivo', 'vob', 'webm', 'wmp', 'wmv', 'yuv',
];
export function getContentTypeRemoteFile(url: string): Promise<string> {
return new Promise((resolve): void => {
const extention = getUrlExtension(url);
if (IMAGE_EXTENSIONS.includes(extention)) {
resolve('image');
return;
}
if (VIDEO_EXTENSIONS.includes(extention)) {
resolve('video');
return;
}
const xhr = new XMLHttpRequest();
xhr.open('HEAD', url, true);
xhr.onreadystatechange = () => {
const contentType = xhr.getResponseHeader('Content-Type');
if (xhr.status > 299) {
xhr.abort();
resolve('unknown');
return;
}
if (contentType) {
xhr.abort();
resolve(contentType.split('/')[0]);
}
};
export function getContentTypeRemoteFile(url: string): 'image' | 'video' | 'unknown' {
const extention = getUrlExtension(url);
if (IMAGE_EXTENSIONS.includes(extention)) {
return 'image';
}
xhr.onerror = () => {
checkCreatingVideoElement(url)
.then(() => {
resolve('video');
})
.catch(() => {
checkCreatingImageElement(url);
})
.then(() => {
resolve('image');
})
.catch(() => {
resolve('unknown');
});
};
if (VIDEO_EXTENSIONS.includes(extention)) {
return 'video';
}
xhr.send();
});
return 'unknown';
}
export function getFileNameFromPath(path: string): string {

@ -8,8 +8,16 @@ context('Connected file share.', () => {
const caseId = '107';
const taskName = `Case ${caseId}`;
const labelName = taskName;
let stdoutToList;
const assetLocalPath = 'mounted_file_share';
const expectedTopLevel = [
{ name: 'images', type: 'DIR', mime_type: 'DIR' },
{ name: 'videos', type: 'DIR', mime_type: 'DIR' },
];
const expectedImagesList = [
{ name: 'image_1.jpg', type: 'REG', mime_type: 'image' },
{ name: 'image_2.jpg', type: 'REG', mime_type: 'image' },
{ name: 'image_3.jpg', type: 'REG', mime_type: 'image' },
];
function createOpenTaskWithShare() {
cy.get('.cvat-create-task-dropdown').click();
@ -20,17 +28,25 @@ context('Connected file share.', () => {
cy.get('.cvat-share-tree')
.should('be.visible')
.within(() => {
cy.intercept('GET', '/api/server/share?**').as('shareRequest');
cy.get('[aria-label="plus-square"]').click();
cy.exec('docker exec -i cvat_server /bin/bash -c "ls ~/share"').then((command) => {
stdoutToList = command.stdout.split('\n');
// [image_case_107_1.png, image_case_107_2.png, image_case_107_3.png]
expect(stdoutToList.length).to.be.eq(3);
// Number of images to select + selection of all images.
cy.get('[title]').should('have.length', stdoutToList.length + 1);
stdoutToList.forEach((el) => {
cy.get(`[title="${el}"]`).should('exist');
// Click on the checkboxes
cy.get(`[title="${el}"]`).prev().click().should('have.attr', 'class').and('contain', 'checked');
cy.wait('@shareRequest').then((interception) => {
expect(interception.response.body
.sort((a, b) => a.name.localeCompare(b.name)))
.to.deep.equal(expectedTopLevel);
});
cy.get('[title="images"]').parent().within(() => {
cy.get('[aria-label="plus-square"]').click();
});
cy.wait('@shareRequest').then((interception) => {
expect(interception.response.body
.sort((a, b) => a.name.localeCompare(b.name)))
.to.deep.equal(expectedImagesList);
});
expectedImagesList.forEach((el) => {
const { name } = el;
cy.get(`[title="${name}"]`).parent().within(() => {
cy.get('.ant-tree-checkbox').click().should('have.attr', 'class').and('contain', 'checked');
});
});
});
@ -48,19 +64,15 @@ context('Connected file share.', () => {
cy.deleteTask(taskName);
});
after(() => {
// Renaming to the original name
cy.exec(`mv ${assetLocalPath}/${stdoutToList[0]}.bk ${assetLocalPath}/${stdoutToList[0]}`);
});
describe(`Testing case "${caseId}"`, () => {
it('Create a task with "Connected file share".', () => {
createOpenTaskWithShare();
cy.openJob();
cy.get('.cvat-player-filename-wrapper').then((playerFilenameWrapper) => {
for (let el = 0; el < stdoutToList.length; el++) {
cy.get(playerFilenameWrapper).should('have.text', stdoutToList[el]);
cy.checkFrameNum(el);
for (let frame = 0; frame < expectedImagesList.length; frame++) {
const { name } = expectedImagesList[frame];
cy.get(playerFilenameWrapper).should('have.text', `${expectedTopLevel[0].name}/${name}`);
cy.checkFrameNum(frame);
cy.get('.cvat-player-next-button').click().trigger('mouseout');
}
});
@ -69,21 +81,22 @@ context('Connected file share.', () => {
it('Check "Fix problem with getting share data for the task when data not available more in Firefox".', () => {
cy.goToTaskList();
createOpenTaskWithShare();
// Rename the image
cy.exec(`mv ${assetLocalPath}/${stdoutToList[0]} ${assetLocalPath}/${stdoutToList[0]}.bk`).then(
(fileRenameCommand) => {
expect(fileRenameCommand.code).to.be.eq(0);
},
);
cy.exec('docker exec -i cvat_server /bin/bash -c "find ~/share -name *.png -type f"').then(
(findFilesCommand) => {
// [image_case_107_2.png, image_case_107_3.png]
expect(findFilesCommand.stdout.split('\n').length).to.be.eq(2);
},
);
cy.intercept('GET', '/api/jobs/*/data?**', {
statusCode: 500,
body: `<!doctype html>
<html lang="en">
<head>
<title>Server Error (500)</title>
</head>
<body>
<h1>Server Error (500)</h1><p></p>
</body>
</html>`,
});
cy.openJob();
cy.get('.cvat-annotation-header').should('exist');
// Error: . "\"No such file or directory /home/django/share/image_case_107_1.png\"".
// Error: . No such file or directory <image_name>".
cy.get('.cvat-notification-notice-fetch-frame-data-from-the-server-failed').should('exist');
cy.closeNotification('.cvat-notification-notice-fetch-frame-data-from-the-server-failed');
});

@ -0,0 +1,200 @@
// Copyright (C) 2022 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
/// <reference types="cypress" />
context('Create mutli tasks.', () => {
const caseId = '118';
const taskName = `Case ${caseId}`;
const labelName = taskName;
const sharePath = 'mounted_file_share';
const expectedTopLevel = [
{ name: 'images', type: 'DIR', mime_type: 'DIR' },
{ name: 'videos', type: 'DIR', mime_type: 'DIR' },
];
const expectedImagesList = [
{ name: 'image_1.jpg', type: 'REG', mime_type: 'image' },
{ name: 'image_2.jpg', type: 'REG', mime_type: 'image' },
{ name: 'image_3.jpg', type: 'REG', mime_type: 'image' },
];
const expectedVideosList = [
{ name: 'video_1.mp4', type: 'REG', mime_type: 'video' },
{ name: 'video_2.mp4', type: 'REG', mime_type: 'video' },
{ name: 'video_3.mp4', type: 'REG', mime_type: 'video' },
];
function submitTask() {
cy.get('.cvat-create-task-content-alert').should('not.exist');
cy.get('.cvat-create-task-content-footer [type="submit"]')
.should('not.be.disabled')
.contains(`Submit ${expectedVideosList.length} tasks`)
.click();
}
function checkCreatedTasks() {
cy.get('.cvat-create-multi-tasks-progress', { timeout: 50000 }).should('exist')
.contains(`Total: ${expectedVideosList.length}`);
cy.contains('button', 'Cancel');
cy.get('.cvat-create-multi-tasks-state').should('exist')
.contains('Finished');
cy.get('.cvat-notification-create-task-success').within(() => {
cy.get('.ant-notification-notice-close').click();
});
cy.contains('button', 'Retry failed tasks').should('be.disabled');
cy.contains('button', 'Ok').click();
expectedVideosList.forEach((video) => {
cy.contains('strong', video.name).should('exist');
});
}
before(() => {
cy.visit('auth/login');
cy.login();
});
beforeEach(() => {
cy.get('.cvat-create-task-dropdown').click();
cy.get('.cvat-create-multi-tasks-button').should('be.visible').click();
cy.addNewLabel(labelName);
});
afterEach(() => {
cy.goToTaskList();
});
describe(`Testing case "${caseId}"`, () => {
it('Checking default name pattern', () => {
cy.get('#name').should('have.attr', 'value', '{{file_name}}');
});
it('Trying to create a tasks with local images', () => {
cy.contains('[role="tab"]', 'My computer').click();
const imageNames = expectedImagesList.map((image) => image.name);
const imagePaths = imageNames.map((name) => `${sharePath}/images/${name}`);
cy.get('input[type="file"]')
.selectFile(imagePaths, { action: 'drag-drop', force: true });
cy.get('.ant-upload-animate').should('not.exist');
cy.get('.cvat-create-task-content-alert').should('be.visible');
cy.get('.cvat-create-task-content-footer [type="submit"]').should('be.disabled');
});
it('Trying to create a tasks with images from the shared storage', () => {
cy.contains('[role="tab"]', 'Connected file share').click();
cy.get('.cvat-share-tree')
.should('be.visible')
.within(() => {
cy.intercept('GET', '/api/server/share?**').as('shareRequest');
cy.get('[aria-label="plus-square"]').click();
cy.wait('@shareRequest').then((interception) => {
expect(interception.response.body
.sort((a, b) => a.name.localeCompare(b.name)))
.to.deep.equal(expectedTopLevel);
});
cy.get('[title="images"]').parent().within(() => {
cy.get('[aria-label="plus-square"]').click();
});
cy.wait('@shareRequest').then((interception) => {
expect(interception.response.body
.sort((a, b) => a.name.localeCompare(b.name)))
.to.deep.equal(expectedImagesList);
});
expectedImagesList.forEach((el) => {
const { name } = el;
cy.get(`[title="${name}"]`).parent().within(() => {
cy.get('.ant-tree-checkbox').click().should('have.attr', 'class').and('contain', 'checked');
});
});
});
cy.get('.cvat-create-task-content-alert').should('be.visible');
cy.get('.cvat-create-task-content-footer [type="submit"]').should('be.disabled');
});
it('Trying to create a tasks with remote images', () => {
const imageUrls =
'https://raw.githubusercontent.com/cvat-ai/cvat/v1.2.0/cvat/apps/documentation/static/documentation/images/cvatt.jpg';
cy.contains('[role="tab"]', 'Remote sources').click();
cy.get('.cvat-file-selector-remote').clear().type(imageUrls);
cy.get('.cvat-create-task-content-alert').should('be.visible');
cy.get('.cvat-create-task-content-footer [type="submit"]').should('be.disabled');
});
it('Trying to create a tasks with local videos', () => {
cy.contains('[role="tab"]', 'My computer').click();
const videoNames = expectedVideosList.map((video) => video.name);
const videoPaths = videoNames.map((name) => `${sharePath}/videos/${name}`);
cy.get('input[type="file"]')
.selectFile(videoPaths, { action: 'drag-drop', force: true });
cy.get('.ant-upload-animate').should('not.exist');
submitTask();
checkCreatedTasks();
});
it('Trying to create a tasks with videos from the shared storage', () => {
cy.contains('[role="tab"]', 'Connected file share').click();
cy.get('.cvat-share-tree')
.should('be.visible')
.within(() => {
cy.intercept('GET', '/api/server/share?**').as('shareRequest');
cy.get('[aria-label="plus-square"]').click();
cy.wait('@shareRequest').then((interception) => {
expect(interception.response.body
.sort((a, b) => a.name.localeCompare(b.name)))
.to.deep.equal(expectedTopLevel);
});
cy.get('[title="videos"]').parent().within(() => {
cy.get('[aria-label="plus-square"]').click();
});
cy.wait('@shareRequest').then((interception) => {
expect(interception.response.body
.sort((a, b) => a.name.localeCompare(b.name)))
.to.deep.equal(expectedVideosList);
});
expectedVideosList.forEach((el) => {
const { name } = el;
cy.get(`[title="${name}"]`).parent().within(() => {
cy.get('.ant-tree-checkbox').click().should('have.attr', 'class').and('contain', 'checked');
});
});
});
submitTask();
checkCreatedTasks();
});
it('Trying to create a tasks with remote videos', () => {
const baseUrl = 'https://github.com/cvat-ai/cvat';
const revision = 'raw/b2a66db76ba8316521bc7de2fbd418008ab3cb5b';
const folder = 'tests/mounted_file_share';
cy.contains('[role="tab"]', 'Remote sources').click();
expectedVideosList.forEach((video) => {
const URL = `${baseUrl}/${revision}/${folder}/${expectedTopLevel[1].name}/${video.name}`;
cy.get('.cvat-file-selector-remote').type(URL).type('{enter}');
});
submitTask();
checkCreatedTasks();
});
});
after(() => {
cy.logout();
cy.getAuthKey().then((authKey) => {
cy.deleteTasks(authKey, expectedVideosList.map((video) => video.name));
});
});
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

@ -133,14 +133,14 @@ class TestTaskUsecases:
"labels": [{"name": "car"}, {"name": "person"}],
},
resource_type=ResourceType.SHARE,
resources=["image_case_107_2.png", "image_case_107_1.png"],
resources=["images/image_1.jpg", "images/image_2.jpg"],
# make sure string fields are transferred correctly;
# see https://github.com/opencv/cvat/issues/4962
data_params={"sorting_method": "lexicographical"},
)
assert task.size == 2
assert task.get_frames_info()[0].name == "image_case_107_1.png"
assert task.get_frames_info()[0].name == "images/image_1.jpg"
assert self.stdout.getvalue() == ""
def test_cant_create_task_with_no_data(self):

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save