Frontend: Improve acceptance test performance #5298

* Tests: improve openNav, and add ability to close all event notifications.
* Tests: improve search with wait for notifications to expire, then using notifications to determine search completion.
* Tests: improve getPhotoCount by using notifications for completion of photo load.  Add fast and slow photo load waits.
* Tests: add fast and slow load detection via notifications
* Tests: replace waits with notification detection
* Tests: refactor tests to work more efficiently with notifications
* Tests: comment out execution time and p-notify__close logging.  Remove slow version of waits.
* Make: allow acceptance tests to be run with --ignore-errors, and make each multi window test run as separate instance
* Frontend: ensure that closing a notification resets the timer for the next notification
* Tests: move notification handling to single notifications.js file
* Tests: move photoCount as changing cards view doesn't generate event when there is one photo
* Tests: remove commented code
* Tests: enable environment variable SHOW_LOGS  (true) to show timings and click actions for waits
* Tests: add Delete, Import, Index, Unstack, and Upload notification waits.
This commit is contained in:
Keith Martin 2025-11-04 20:29:37 +10:00 committed by GitHub
parent 5afb634edf
commit 6d0016b1df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 268 additions and 77 deletions

View file

@ -364,16 +364,20 @@ test-js:
(cd frontend && npm run test)
acceptance:
$(info Running public-mode tests in Chrome...)
(cd frontend && npm run testcafe -- "chrome --headless=new" --test-grep "^(Multi-Window)\:*" --test-meta mode=public --config-file ./testcaferc.json --experimental-multiple-windows "tests/acceptance" && npm run testcafe -- "chrome --headless=new" --test-grep "^(Common|Core)\:*" --test-meta mode=public --config-file ./testcaferc.json "tests/acceptance")
(cd frontend && find ./tests/acceptance -type f -name "*.js" | xargs -i perl -0777 -ne 'while(/(?:mode: \"auth[^,]*\,)|(Multi-Window\:[A-Za-z 0-9\-_]*)/g){print "$$1\n" if ($$1);}' {} | xargs -I testname bash -c 'npm run testcafe -- "chrome --headless=new" --experimental-multiple-windows --test-meta mode=public --config-file ./testcaferc.json --test "testname" "tests/acceptance"')
(cd frontend && npm run testcafe -- "chrome --headless=new" --test-grep "^(Common|Core)\:*" --test-meta mode=public --config-file ./testcaferc.json "tests/acceptance")
acceptance-short:
$(info Running JS acceptance tests in Chrome...)
(cd frontend && npm run testcafe -- "chrome --headless=new" --test-grep "^(Multi-Window)\:*" --test-meta mode=public --config-file ./testcaferc.json --experimental-multiple-windows "tests/acceptance" && npm run testcafe -- "chrome --headless=new" --test-grep "^(Common|Core)\:*" --test-meta mode=public,type=short --config-file ./testcaferc.json "tests/acceptance")
(cd frontend && find ./tests/acceptance -type f -name "*.js" | xargs -i perl -0777 -ne 'while(/(?:mode: \"auth[^,]*\,)|(Multi-Window\:[A-Za-z 0-9\-_]*)/g){print "$$1\n" if ($$1);}' {} | xargs -I testname bash -c 'npm run testcafe -- "chrome --headless=new" --experimental-multiple-windows --test-meta mode=public,type=short --config-file ./testcaferc.json --test "testname" "tests/acceptance"')
(cd frontend && npm run testcafe -- "chrome --headless=new" --test-grep "^(Common|Core)\:*" --test-meta mode=public,type=short --config-file ./testcaferc.json "tests/acceptance")
acceptance-auth:
$(info Running JS acceptance-auth tests in Chrome...)
(cd frontend && npm run testcafe -- "chrome --headless=new" --test-grep "^(Multi-Window)\:*" --test-meta mode=auth --config-file ./testcaferc.json --experimental-multiple-windows "tests/acceptance" && npm run testcafe -- "chrome --headless=new" --test-grep "^(Common|Core)\:*" --test-meta mode=auth --config-file ./testcaferc.json "tests/acceptance")
(cd frontend && find ./tests/acceptance -type f -name "*.js" | xargs -i perl -0777 -ne 'while(/(?:mode: \"public[^,]*\,)|(Multi-Window\:[A-Za-z 0-9\-_]*)/g){print "$$1\n" if ($$1);}' {} | xargs -I testname bash -c 'npm run testcafe -- "chrome --headless=new" --experimental-multiple-windows --test-meta mode=auth --config-file ./testcaferc.json --test "testname" "tests/acceptance"')
(cd frontend && npm run testcafe -- "chrome --headless=new" --test-grep "^(Common|Core)\:*" --test-meta mode=auth --config-file ./testcaferc.json "tests/acceptance")
acceptance-auth-short:
$(info Running JS acceptance-auth tests in Chrome...)
(cd frontend && npm run testcafe -- "chrome --headless=new" --test-grep "^(Multi-Window)\:*" --test-meta mode=auth --config-file ./testcaferc.json --experimental-multiple-windows "tests/acceptance" && npm run testcafe -- "chrome --headless=new" --test-grep "^(Common|Core)\:*" --test-meta mode=auth,type=short --config-file ./testcaferc.json "tests/acceptance")
(cd frontend && find ./tests/acceptance -type f -name "*.js" | xargs -i perl -0777 -ne 'while(/(?:mode: \"public[^,]*\,)|(Multi-Window\:[A-Za-z 0-9\-_]*)/g){print "$$1\n" if ($$1);}' {} | xargs -I testname bash -c 'npm run testcafe -- "chrome --headless=new" --experimental-multiple-windows --test-meta mode=auth,type=short --config-file ./testcaferc.json --test "testname" "tests/acceptance"')
(cd frontend && npm run testcafe -- "chrome --headless=new" --test-grep "^(Common|Core)\:*" --test-meta mode=auth,type=short --config-file ./testcaferc.json "tests/acceptance")
vitest-watch:
$(info Running Vitest unit tests in watch mode...)
(cd frontend && npm run test-watch)

View file

@ -46,6 +46,7 @@ export default {
color: "transparent",
text: "",
delay: this.defaultDelay,
timer: 0,
},
lastText: "",
lastId: 1,
@ -127,6 +128,7 @@ export default {
this.lastId++;
this.lastText = text;
let timer = 0;
const m = {
id: this.lastId,
@ -134,6 +136,7 @@ export default {
icon,
text,
delay,
timer,
};
this.messages.push(m);
@ -144,6 +147,9 @@ export default {
},
showNext() {
const message = this.messages.shift();
if (this.message.timer > 0) {
clearTimeout(this.message.timer);
};
if (message) {
this.message = message;
@ -166,14 +172,16 @@ export default {
this.visible = true;
setTimeout(() => {
this.message.timer = setTimeout(() => {
this.lastText = "";
this.message.timer = 0;
this.showNext();
}, this.message.delay);
} else {
this.lastText = "";
this.visible = false;
this.message.text = "";
this.message.timer = 0;
}
},
},

View file

@ -9,6 +9,7 @@ import PhotoViewer from "../page-model/photoviewer";
import Page from "../page-model/page";
import AlbumDialog from "../page-model/dialog-album";
import PhotoEdit from "../page-model/photo-edit";
import Notifies from "../page-model/notifications";
fixture`Test albums`.page`${testcafeconfig.url}`;
@ -21,6 +22,7 @@ const photoviewer = new PhotoViewer();
const page = new Page();
const albumdialog = new AlbumDialog();
const photoedit = new PhotoEdit();
const notifies = new Notifies();
test.meta("testID", "albums-001").meta({ type: "short", mode: "public" })(
"Common: Create/delete album on /albums",
@ -152,6 +154,7 @@ test.meta("testID", "albums-004").meta({ type: "short", mode: "public" })(
async (t) => {
// Get initial counts for both Holiday and Christmas albums
await menu.openPage("albums");
await notifies.waitForPhotosToLoad(2000);
await toolbar.search("Holiday");
const HolidayAlbumUid = await album.getNthAlbumUid("all", 0);
await album.openAlbumWithUid(HolidayAlbumUid);

View file

@ -97,8 +97,8 @@ test.meta("testID", "labels-003").meta({ mode: "public" })("Common: Rename Label
await toolbar.search("zebra");
const LabelZebraUid = await label.getNthLabeltUid(0);
await label.openNthLabel(0);
const FirstPhotoZebraUid = await photo.getNthPhotoUid("all", 0);
const PhotoCountZebra = await photo.getPhotoCount("all");
const FirstPhotoZebraUid = await photo.getNthPhotoUid("all", 0);
await menu.openPage("labels");
await toolbar.search("zebra");
await t

View file

@ -4,6 +4,7 @@ import Menu from "../../page-model/menu";
import Toolbar from "../../page-model/toolbar";
import Page from "../../page-model/page";
import Library from "../../page-model/library";
import Notifies from "../../page-model/notifications";
fixture`Import file from folder`.page`${testcafeconfig.url}`;
@ -11,6 +12,7 @@ const menu = new Menu();
const toolbar = new Toolbar();
const page = new Page();
const library = new Library();
const notifies = new Notifies();
test.meta("testID", "library-import-001").meta({ type: "short", mode: "public" })(
"Common: Import files from folder using copy",
@ -27,9 +29,8 @@ test.meta("testID", "library-import-001").meta({ type: "short", mode: "public" }
.wait(9000)
.typeText(library.openImportFolderSelect, "/Bäcke", { replace: true })
.click(page.selectOption.nth(0))
.click(library.import)
//TODO replace wait
.wait(60000);
.click(library.import);
await notifies.waitForImport(60000);
await menu.openPage("labels");
if (t.browser.platform === "mobile") {
await t.eval(() => location.reload());

View file

@ -6,6 +6,7 @@ import Photo from "../../page-model/photo";
import Page from "../../page-model/page";
import Library from "../../page-model/library";
import Album from "../../page-model/album";
import Notifies from "../../page-model/notifications";
fixture`Test index`.page`${testcafeconfig.url}`;
@ -15,6 +16,7 @@ const photo = new Photo();
const page = new Page();
const library = new Library();
const album = new Album();
const notifies = new Notifies();
test.meta("testID", "library-index-001").meta({ type: "short", mode: "public" })(
"Common: Index files from folder",
@ -66,9 +68,8 @@ test.meta("testID", "library-index-001").meta({ type: "short", mode: "public" })
.click(library.indexFolderSelect)
.typeText(library.indexFolderSelect, "/mo", { replace: true })
.click(page.selectOption.withText("/moment"))
.click(library.index)
//TODO replace wait
.wait(50000);
.click(library.index);
await notifies.waitForIndexing(50000);
await t.expect(Selector("span").withText("Done.").visible, { timeout: 60000 }).ok();

View file

@ -6,6 +6,7 @@ import ContextMenu from "../page-model/context-menu";
import Photo from "../page-model/photo";
import Subject from "../page-model/subject";
import PhotoEdit from "../page-model/photo-edit";
import Notifies from "../page-model/notifications";
fixture`Test people`.page`${testcafeconfig.url}`;
@ -15,6 +16,7 @@ const contextmenu = new ContextMenu();
const photo = new Photo();
const subject = new Subject();
const photoedit = new PhotoEdit();
const notifies = new Notifies();
test.meta("testID", "people-001").meta({ type: "short", mode: "public" })(
"Common: Add name to new face and rename subject",
@ -41,8 +43,9 @@ test.meta("testID", "people-001").meta({ type: "short", mode: "public" })(
await t.click(subject.recognizedTab);
await subject.checkFaceVisibility(FirstFaceID, false);
await notifies.closeAllEventPopups();
await t.eval(() => location.reload());
await t.wait(6000);
await notifies.waitForPeopleToLoad(6000, true);
const SubjectCountAfterAdd = await subject.getSubjectCount();
await t.expect(SubjectCountAfterAdd).eql(SubjectCount + 1);
@ -92,7 +95,7 @@ test.meta("testID", "people-001").meta({ type: "short", mode: "public" })(
await t.expect(photoedit.inputName.nth(0).value).contains("Max Mu").click(photoedit.dialogClose);
await contextmenu.clearSelection();
await toolbar.search("person:max-mu");
await toolbar.search("person:max-mu", false);
const PhotosInSubjectAfterRenameCount = await photo.getPhotoCount("all");
await t.expect(PhotosInSubjectAfterRenameCount).eql(PhotosInSubjectCount);
}
@ -111,8 +114,7 @@ test.meta("testID", "people-002").meta({ type: "short", mode: "public" })(
const AndreaUID = await subject.getNthSubjectUid(0);
await subject.openSubjectWithUid(AndreaUID);
await t.eval(() => location.reload());
await t.wait(5000);
const PhotosInAndreaCount = await photo.getPhotoCount("all");
const PhotosInAndreaCount = await photo.getPhotoCount("all", 13000);
await photo.triggerHoverAction("nth", 1, "select");
await contextmenu.triggerContextMenuAction("edit", "");
await t
@ -129,13 +131,11 @@ test.meta("testID", "people-002").meta({ type: "short", mode: "public" })(
.click(photoedit.dialogClose);
await contextmenu.clearSelection();
await t.eval(() => location.reload());
await t.wait(5000);
const PhotosInAndreaAfterRejectCount = await photo.getPhotoCount("all");
const PhotosInAndreaAfterRejectCount = await photo.getPhotoCount("all", 13000);
const Diff = PhotosInAndreaCount - PhotosInAndreaAfterRejectCount;
await toolbar.search("person:nicole");
await t.eval(() => location.reload());
await t.wait(5000);
const PhotosInNicoleCount = await photo.getPhotoCount("all");
const PhotosInNicoleCount = await photo.getPhotoCount("all", 13000);
await t.expect(Diff).gte(PhotosInNicoleCount);
}
@ -200,8 +200,9 @@ test.meta("testID", "people-005").meta({ mode: "public" })("Common: Remove face"
await t.click(photoedit.dialogClose);
await contextmenu.clearSelection();
await notifies.closeAllEventPopups();
await t.eval(() => location.reload());
await t.wait(5000);
await notifies.waitForPhotosToLoad(5000, true);
await photo.triggerHoverAction("uid", FirstPhotoUid, "select");
await contextmenu.triggerContextMenuAction("edit", "");
await t.click(photoedit.peopleTab);
@ -234,17 +235,20 @@ test.meta("testID", "people-006").meta({ mode: "public" })("Common: Hide face",
const FirstFaceID = await subject.getNthFaceUid(0);
await subject.checkFaceVisibility(FirstFaceID, true);
await subject.triggerHoverAction("id", FirstFaceID, "hidden");
await notifies.closeAllEventPopups();
await t.eval(() => location.reload());
await t.wait(5000);
await notifies.waitForPeopleToLoad(5000, true);
await subject.checkFaceVisibility(FirstFaceID, false);
await subject.triggerToolbarAction("show-hidden");
await notifies.closeAllEventPopups();
await t.eval(() => location.reload());
await t.wait(6000);
await notifies.waitForPeopleToLoad(6000, true);
await subject.checkFaceVisibility(FirstFaceID, true);
await subject.triggerHoverAction("id", FirstFaceID, "hidden");
await subject.triggerToolbarAction("exclude-hidden");
await notifies.closeAllEventPopups();
await t.eval(() => location.reload());
await t.wait(6000);
await notifies.waitForPeopleToLoad(6000, true);
await subject.checkFaceVisibility(FirstFaceID, true);
});
@ -254,28 +258,33 @@ test.meta("testID", "people-007").meta({ mode: "public" })("Common: Hide person"
const FirstPersonUid = await subject.getNthSubjectUid(0);
await subject.checkSubjectVisibility("uid", FirstPersonUid, true);
await subject.triggerHoverAction("uid", FirstPersonUid, "hidden");
await notifies.closeAllEventPopups();
await t.eval(() => location.reload());
await t.wait(6000);
await notifies.waitForPeopleToLoad(6000, true);
await subject.checkSubjectVisibility("uid", FirstPersonUid, false);
await subject.triggerToolbarAction("show-hidden");
await notifies.closeAllEventPopups();
await t.eval(() => location.reload());
await t.wait(6000);
await notifies.waitForPeopleToLoad(6000, true);
await subject.checkSubjectVisibility("uid", FirstPersonUid, true);
await subject.triggerHoverAction("uid", FirstPersonUid, "hidden");
await subject.triggerToolbarAction("exclude-hidden");
await notifies.closeAllEventPopups();
await t.eval(() => location.reload());
await t.wait(5000);
await notifies.waitForPeopleToLoad(5000, true);
await subject.checkSubjectVisibility("uid", FirstPersonUid, true);
});
test.meta("testID", "people-008").meta({ mode: "public" })("Common: Go to person from face menu", async (t) => {
await menu.openPage("people");
await notifies.closeAllEventPopups();
await t.click(subject.recognizedTab);
await t.wait(2000);
await notifies.waitForPeopleToLoad(2000, true);
const firstPersonUid = await subject.getNthSubjectUid(0);
await notifies.closeAllEventPopups();
await subject.openSubjectWithUid(firstPersonUid);
await t.wait(2000);
await notifies.waitForPhotosToLoad(2000, true);
await photo.triggerHoverAction("nth", 0, "select");
await contextmenu.triggerContextMenuAction("edit", "");
await t.click(photoedit.peopleTab);
@ -293,8 +302,9 @@ test.meta("testID", "people-008").meta({ mode: "public" })("Common: Go to person
test.meta("testID", "people-009").meta({ mode: "public" })("Common: Set person cover from face menu", async (t) => {
await menu.openPage("people");
await notifies.closeAllEventPopups();
await t.click(subject.recognizedTab);
await t.wait(3000);
await notifies.waitForPeopleToLoad(3000, true);
const firstPersonUid = await subject.getNthSubjectUid(0);
const personCard = Selector("div.result.is-subject[data-uid='" + firstPersonUid + "']");
@ -305,9 +315,8 @@ test.meta("testID", "people-009").meta({ mode: "public" })("Common: Set person c
.ok(`Could not get initial thumbnail for person ${firstPersonUid}`);
await subject.openSubjectWithUid(firstPersonUid);
await t.wait(2000);
const photoCount = await photo.getPhotoCount("all");
const photoCount = await photo.getPhotoCount("all", 9000);
const photoIdx = photoCount > 1 ? 1 : 0;
await photo.triggerHoverAction("nth", photoIdx, "select");
await contextmenu.triggerContextMenuAction("edit", "");
@ -315,15 +324,17 @@ test.meta("testID", "people-009").meta({ mode: "public" })("Common: Set person c
const faceName = await photoedit.inputName.nth(0).value;
if (faceName && faceName !== "") {
await notifies.closeAllEventPopups();
await photoedit.setPersonCover(0);
await t.wait(2000);
await notifies.waitForPersonCoverUpdate(2000, true);
await t.click(photoedit.dialogClose);
await contextmenu.clearSelection();
await menu.openPage("people");
await notifies.closeAllEventPopups();
await t.click(subject.recognizedTab);
await t.wait(3000);
await notifies.waitForPeopleToLoad(3000, true);
const updatedThumb = await personCard.find("div.preview img").getAttribute("src");
await t

View file

@ -10,6 +10,7 @@ import PhotoEdit from "../page-model/photo-edit";
import Originals from "../page-model/originals";
import Album from "../page-model/album";
import Library from "../page-model/library";
import Notifies from "../page-model/notifications";
fixture`Test photos upload and delete`.page`${testcafeconfig.url}`;
@ -22,6 +23,7 @@ const page = new Page();
const photoedit = new PhotoEdit();
const originals = new Originals();
const library = new Library();
const notifies = new Notifies();
test.meta("testID", "photos-upload-delete-001").meta({ type: "short", mode: "public" })(
"Core: Upload + Delete jpg/json",
@ -32,7 +34,7 @@ test.meta("testID", "photos-upload-delete-001").meta({ type: "short", mode: "pub
await menu.openNav();
const InitialOriginalsCount = await Selector("a.nav-originals span.nav-count-item", { timeout: 5000 }).innerText;
await t.expect(fs.existsSync("../storage/acceptance/originals/2020/10")).notOk();
await toolbar.search("digikam");
await toolbar.search("digikam", false);
const PhotoCount = await photo.getPhotoCount("all");
await t.expect(PhotoCount).eql(0);
@ -42,8 +44,8 @@ test.meta("testID", "photos-upload-delete-001").meta({ type: "short", mode: "pub
.setFilesToUpload(Selector(".input-upload"), [
"../../upload-files/digikam.jpg",
"../../upload-files/digikam.json",
])
.wait(15000);
]);
await notifies.waitForUpload();
await toolbar.triggerToolbarAction("refresh");
const PhotoCountAfterUpload = await photo.getPhotoCount("all");
@ -51,7 +53,7 @@ test.meta("testID", "photos-upload-delete-001").meta({ type: "short", mode: "pub
const UploadedPhoto = await photo.getNthPhotoUid("all", 0);
await t.navigateTo("/library/index/files/2020/10");
await t.wait(5000);
await notifies.waitForFoldersToLoad(5000, true);
const FileCount = await originals.getFileCount();
await t.expect(FileCount).eql(2);
@ -106,13 +108,14 @@ test.meta("testID", "photos-upload-delete-002").meta({ mode: "public" })("Core:
console.log("Skipped on mobile");
} else {
await t.expect(fs.existsSync("../storage/acceptance/originals/2020/06")).notOk();
await toolbar.search("korn");
await toolbar.search("korn", false);
const PhotoCount = await photo.getPhotoCount("all");
await t.expect(PhotoCount).eql(0);
await toolbar.triggerToolbarAction("upload");
await t.setFilesToUpload(Selector(".input-upload"), ["../../upload-files/korn.mp4"]).wait(15000);
await t.setFilesToUpload(Selector(".input-upload"), ["../../upload-files/korn.mp4"]);
await notifies.waitForUpload();
await toolbar.triggerToolbarAction("refresh");
const PhotoCountAfterUpload = await photo.getPhotoCount("all");
@ -120,7 +123,7 @@ test.meta("testID", "photos-upload-delete-002").meta({ mode: "public" })("Core:
const UploadedPhoto = await photo.getNthPhotoUid("all", 0);
await t.navigateTo("/library/index/files/2020/06");
await t.wait(5000);
await notifies.waitForFoldersToLoad(5000, true);
const FileCount = await originals.getFileCount();
@ -180,10 +183,10 @@ test.meta("testID", "photos-upload-delete-003").meta({ mode: "public" })(
await album.openAlbumWithUid(AlbumUid);
const PhotoCount = await photo.getPhotoCount("all");
await toolbar.triggerToolbarAction("upload");
await t.setFilesToUpload(Selector(".input-upload"), ["../../upload-files/ladybug.jpg"]).wait(15000);
await t.setFilesToUpload(Selector(".input-upload"), ["../../upload-files/ladybug.jpg"]);
await notifies.waitForUpload();
await toolbar.triggerToolbarAction("refresh");
await t.wait(5000);
const PhotoCountAfterUpload = await photo.getPhotoCount("all");
const PhotoCountAfterUpload = await photo.getPhotoCount("all", 12000);
await t.expect(PhotoCountAfterUpload).eql(PhotoCount + 1);
@ -221,8 +224,8 @@ test.meta("testID", "photos-upload-delete-004").meta({ mode: "public" })(
.click(Selector(".input-albums"))
.typeText(Selector(".input-albums input"), "NewCreatedAlbum")
.pressKey("enter")
.setFilesToUpload(Selector(".input-upload"), ["../../upload-files/digikam.jpg"])
.wait(15000);
.setFilesToUpload(Selector(".input-upload"), ["../../upload-files/digikam.jpg"]);
await notifies.waitForUpload();
if (t.browser.platform === "mobile") {
await t.eval(() => location.reload());
} else {
@ -252,8 +255,8 @@ test.meta("testID", "photos-upload-delete-004").meta({ mode: "public" })(
await menu.openPage("albums");
await toolbar.search("NewCreatedAlbum");
await album.openNthAlbum(0);
await photo.checkPhotoVisibility(UploadedPhotoUid, false);
const PhotoCountAfterDelete = await photo.getPhotoCount("all");
await photo.checkPhotoVisibility(UploadedPhotoUid, false);
await t.expect(PhotoCountAfterDelete).eql(0);
@ -273,7 +276,8 @@ test.meta("testID", "photos-upload-delete-005").meta({ type: "short", mode: "pub
console.log("Skipped on mobile");
} else {
await toolbar.triggerToolbarAction("upload");
await t.setFilesToUpload(Selector(".input-upload"), ["../../upload-files/hentai_2.jpg"]).wait(15000);
await t.setFilesToUpload(Selector(".input-upload"), ["../../upload-files/hentai_2.jpg"]);
await notifies.waitForUploadFailed();
await t.click(Selector("button.action-close")).wait(8000);
await menu.openPage("library");
@ -297,7 +301,8 @@ test.meta("testID", "photos-upload-delete-006").meta({ type: "short", mode: "pub
await menu.openPage("browse");
await toolbar.triggerToolbarAction("upload");
await t.setFilesToUpload(Selector(".input-upload"), ["../../upload-files/foo.txt"]).wait(15000);
await t.setFilesToUpload(Selector(".input-upload"), ["../../upload-files/foo.txt"]);
await notifies.waitForUpload();
await menu.openNav();
const OriginalsCountAfterUpload = await Selector("a.nav-originals span.nav-count-item", {
timeout: 10000,

View file

@ -9,6 +9,7 @@ import PhotoEdit from "../page-model/photo-edit";
import Subject from "../page-model/subject";
import Label from "../page-model/label";
import Library from "../page-model/library";
import Notifies from "../page-model/notifications";
fixture`Test Keyboard Shortcuts`.page`${testcafeconfig.url}`;
@ -21,6 +22,7 @@ const photoEdit = new PhotoEdit();
const subject = new Subject();
const label = new Label();
const library = new Library();
const notifies = new Notifies();
const triggerKeyPress = ClientFunction((key, code, keyCode, ctrlKey, shiftKey, targetSelector) => {
const target = targetSelector ? document.querySelector(targetSelector) : document;
@ -61,8 +63,9 @@ test.meta("testID", "shortcuts-001").meta({ type: "short", mode: "public" })(
const initialScrollY = await getcurrentPosition();
await t.expect(initialScrollY).gt(0, "Should have scrolled down before refresh");
await notifies.closeAllEventPopups();
await triggerKeyPress("r", "KeyR", 82, true, false);
await t.wait(2000); // Wait for page to reload
await notifies.waitForPhotosToLoad(2000, true); // Wait for page to reload
const finalScrollY = await getcurrentPosition();
await t.expect(finalScrollY).eql(initialScrollY, "Scroll position should be restored after refresh");
@ -128,15 +131,11 @@ test.meta("testID", "shortcuts-003").meta({ type: "short", mode: "public" })(
await triggerKeyPress("a", "KeyA", 65, true, false);
await t.expect(Selector("div.p-notify--success").withText("Archived").visible).ok();
await t.wait(5000);
await t.click(Selector("div.p-notify--success").withText("Archived"));
await triggerKeyPress("a", "KeyA", 65, true, false);
await t.expect(Selector("div.p-notify--success").withText("Restored").visible).ok();
await t.wait(5000);
await t.click(Selector("div.p-notify--success").withText("Restored"));
await triggerKeyPress("d", "KeyD", 68, true, false);
await t.expect(Selector("div.p-notify--success").withText("Downloading").visible).ok();

View file

@ -7,6 +7,7 @@ import PhotoViewer from "../page-model/photoviewer";
import Page from "../page-model/page";
import PhotoEdit from "../page-model/photo-edit";
import Library from "../page-model/library";
import Notifies from "../page-model/notifications";
fixture`Test stacks`.page`${testcafeconfig.url}`;
@ -17,6 +18,7 @@ const photoviewer = new PhotoViewer();
const page = new Page();
const photoedit = new PhotoEdit();
const library = new Library();
const notifies = new Notifies();
test.meta("testID", "stacks-001").meta({ type: "short", mode: "public" })(
"Common: View all files of a stack",
@ -57,9 +59,9 @@ test.meta("testID", "stacks-002").meta({ type: "short", mode: "public" })("Commo
});
test.meta("testID", "stacks-003").meta({ type: "short", mode: "public" })("Common: Ungroup files", async (t) => {
await toolbar.search("group");
await t.click(toolbar.cardsViewAction);
await toolbar.search("group", false);
const PhotoCount = await photo.getPhotoCount("all");
await t.click(toolbar.cardsViewAction);
const SequentialPhotoUid = await photo.getNthPhotoUid("all", 0);
await t.expect(PhotoCount).eql(1);
@ -72,9 +74,9 @@ test.meta("testID", "stacks-003").meta({ type: "short", mode: "public" })("Commo
.click(photoedit.filesTab)
.click(photoedit.toggleExpandFile.nth(0))
.click(photoedit.toggleExpandFile.nth(1))
.click(photoedit.unstackFile)
.wait(12000)
.click(photoedit.dialogClose);
.click(photoedit.unstackFile);
await notifies.waitForUnstack();
await t.click(photoedit.dialogClose);
await menu.openPage("browse");
await toolbar.search("group");
if (t.browser.platform === "mobile") {
@ -94,12 +96,12 @@ test.meta("testID", "stacks-004").meta({ mode: "public" })("Common: Delete non p
.click(library.importTab)
.click(library.openImportFolderSelect)
.click(page.selectOption.withText("/pizza"))
.click(library.import)
.wait(10000);
.click(library.import);
await notifies.waitForImport();
await menu.openPage("browse");
await toolbar.search("pizza");
await t.click(toolbar.cardsViewAction);
await toolbar.search("pizza", false);
const PhotoCount = await photo.getPhotoCount("all");
await t.click(toolbar.cardsViewAction);
const PhotoUid = await photo.getNthPhotoUid("all", 0);
await t.expect(PhotoCount).eql(1);
@ -113,8 +115,8 @@ test.meta("testID", "stacks-004").meta({ mode: "public" })("Common: Delete non p
await t
.click(photoedit.toggleExpandFile.nth(1))
.click(Selector(photoedit.deleteFile))
.click(Selector(".action-confirm"))
.wait(10000);
.click(Selector(".action-confirm"));
await notifies.waitForFileDeleted();
const FileCountAfterDeletion = await photoedit.getFileCount();
await t.expect(FileCountAfterDeletion).eql(1);

View file

@ -1,18 +1,28 @@
import { Selector, t } from "testcafe";
const showLogs = process.env.SHOW_LOGS == "true";
export default class Page {
constructor() {
this.navDrawer = Selector(".v-navigation-drawer");
this.navActive = Selector(".v-navigation-drawer--active");
this.navInRail = Selector(".v-navigation-drawer--rail");
this.expandButton = Selector("div.nav-expand i");
this.expandButtonContainer = Selector("div.nav-expand");
}
async openNav() {
if (await this.expandButton.visible) {
await t.click(this.expandButton);
} else if (await this.expandButtonContainer.visible) {
await t.click(this.expandButton);
showLogs && console.time("openNav")
if (await this.navActive.visible) { // Make sure that the nav has been rendered
if (await this.navInRail.exists) { // fail fast looking for a minimized nav
if (await this.expandButton.exists) {
await t.click(this.expandButton);
} else if (await this.expandButtonContainer.exists) {
await t.click(this.expandButton);
}
}
}
showLogs && console.timeEnd("openNav")
}
async openPage(page) {

View file

@ -0,0 +1,140 @@
import { Selector, t } from "testcafe";
const showLogs = process.env.SHOW_LOGS == "true";
const waitAfterClick = 350 // Please note that all t.click have to wait to allow the clicked item time to go away (300ms fade out).
export default class Page {
constructor() {
this.notifyClose250 = Selector(".p-notify__close", {timeout: 250})
}
logMessage(message) {
var now = new Date();
console.log(now.toISOString() + " " + message);
}
// closeAllEventPopups will close any event popups that are open, ignoring any click issues.
async closeAllEventPopups() {
showLogs && console.time("closeAllEventPopups");
showLogs && this.logMessage("Before While in closeAllEventPopups");
while(await this.notifyClose250.visible) {
try {
showLogs && this.logMessage("Before Click in closeAllEventPopups");
await t.click(this.notifyClose250).wait(waitAfterClick);
showLogs && this.logMessage("After Click in closeAllEventPopups");
} catch {
showLogs && this.logMessage("After Click In Catch in closeAllEventPopups");
showLogs && console.trace("notify close missed in closeAllEventPopups")
}
}
showLogs && console.timeEnd("closeAllEventPopups");
}
// waitForSpecficEvent will wait for the event to show up, for delay amount of time (after closing any event messages that do not match).
async waitForSpecficEvent(event, delay = 7000, close = true) {
showLogs && this.logMessage("Before While in waitForSpecficEvent");
while(await this.notifyClose250.visible) {
if (await Selector("div.p-notify__text", {timeout: 50}).withText(event).visible) {
try {
if (close) {
showLogs && this.logMessage("Before Click in waitForSpecficEvent");
await t.click(this.notifyClose250).wait(waitAfterClick);
showLogs && this.logMessage("After Click in waitForSpecficEvent");
}
} catch {
// ignore the error as the item may not show up
showLogs && this.logMessage("After Click In Catch in waitForSpecficEvent");
console.trace("notify close missed in waitForSpecficEvent " + event)
} finally {
return
}
}
try {
showLogs && this.logMessage("Before Click in waitForSpecficEvent");
await t.click(this.notifyClose250).wait(waitAfterClick);
showLogs && this.logMessage("After Click in waitForSpecficEvent");
} catch {
showLogs && this.logMessage("After Click In Catch in waitForSpecficEvent");
showLogs && console.trace("notify close missed in waitForSpecficEvent Pre")
}
}
showLogs && this.logMessage("Before Visible in waitForSpecficEvent");
if ((await Selector("div.p-notify__text", {timeout: delay}).withText(event).visible) && close){
try {
showLogs && this.logMessage("Before Click in waitForSpecficEvent");
await t.click(this.notifyClose250).wait(waitAfterClick);
showLogs && this.logMessage("After Click in waitForSpecficEvent");
} catch {
// ignore the error as the item may not show up
showLogs && this.logMessage("After Click In Catch in waitForSpecficEvent");
showLogs && console.trace("notify close missed in waitForSpecficEvent")
}
}
}
async waitForFileDeleted(delay = 10000, close = true) {
showLogs && console.time("waitForFileDeleted")
await this.waitForSpecficEvent("File deleted", delay, close);
showLogs && console.timeEnd("waitForFileDeleted")
}
async waitForFoldersToLoad(delay, close) {
showLogs && console.time("waitForFoldersToLoad")
await this.waitForSpecficEvent(/[fF]older/, delay, close);
showLogs && console.timeEnd("waitForFoldersToLoad")
}
async waitForImport(delay = 10000, close = true) {
showLogs && console.time("waitForImport")
await this.waitForSpecficEvent("Import completed in", delay, close);
showLogs && console.timeEnd("waitForImport")
}
async waitForIndexing(delay = 10000, close = true) {
showLogs && console.time("waitForIndexing")
await this.waitForSpecficEvent("Indexing completed in", delay, close);
showLogs && console.timeEnd("waitForIndexing")
}
async waitForPeopleToLoad(delay, close = true) {
showLogs && console.time("waitForPeopleToLoad")
await this.waitForSpecficEvent(/(people|person) (found|loaded)/, delay, close);
showLogs && console.timeEnd("waitForPeopleToLoad")
}
async waitForPersonCoverUpdate(delay, close = true) {
showLogs && console.time("waitForPersonCoverUpdate")
await this.waitForSpecficEvent("Person cover updated", delay, close);
showLogs && console.timeEnd("waitForPersonCoverUpdate")
}
async waitForPhotosToLoad(delay, close = true){
showLogs && console.time("waitForPhotosToLoad")
await this.waitForSpecficEvent(/(picture|pictures) found/, delay, close);
showLogs && console.timeEnd("waitForPhotosToLoad")
}
async waitForSearchToFinish(delay, close = true){
showLogs && console.time("waitForSearchToFinish")
await this.waitForSpecficEvent(/(found|contain|empty)/, delay, close);
showLogs && console.timeEnd("waitForSearchToFinish")
}
async waitForUnstack(delay = 12000, close = true) {
showLogs && console.time("waitForUnstack")
await this.waitForSpecficEvent("File removed from stack", delay, close);
showLogs && console.timeEnd("waitForUnstack")
}
async waitForUpload(delay = 15000, close = true) {
showLogs && console.time("waitForUpload")
await this.waitForSpecficEvent("Upload has been processed", delay, close);
showLogs && console.timeEnd("waitForUpload")
}
async waitForUploadFailed(delay = 15000, close = true) {
showLogs && console.time("waitForUploadFailed")
await this.waitForSpecficEvent("Upload failed", delay, close);
showLogs && console.timeEnd("waitForUploadFailed")
}
}

View file

@ -1,7 +1,7 @@
import { Selector, t } from "testcafe";
import Toolbar from "./toolbar";
import Notifies from "../page-model/notifications";
const toolbar = new Toolbar();
const notifies = new Notifies();
export default class Page {
constructor() {}
@ -18,8 +18,8 @@ export default class Page {
}
}
async getPhotoCount(type) {
await t.wait(7000);
async getPhotoCount(type, delay = 7000) {
await notifies.waitForPhotosToLoad(delay, true)
if (type === "all") {
const PhotoCount = await Selector("div.is-photo", { timeout: 2000 }).count;
return PhotoCount;

View file

@ -1,4 +1,6 @@
import { Selector, t } from "testcafe";
import Notifies from "../page-model/notifications";
const notifies = new Notifies();
export default class Page {
constructor() {
@ -137,8 +139,13 @@ export default class Page {
}
}
async search(term) {
await t.click(this.search1).typeText(this.search1, term, { replace: true }).pressKey("enter").wait(7000);
async search(term, wait = true) {
await notifies.closeAllEventPopups();
await t.click(this.search1).typeText(this.search1, term, { replace: true }).pressKey("enter");
if (wait) {
await notifies.waitForSearchToFinish(7000);
}
}
async setFilter(filter, option) {