Browse Source

Tests: Add acceptance tests

theresa 3 years ago
parent
commit
4455f11d58

+ 13 - 13
frontend/tests/acceptance-private/admin-role.js

@@ -16,23 +16,23 @@ test.meta("testID", "admin-role-001")("Access to settings", async (t) => {
   await page.login("admin", "photoprism");
   await page.openNav();
   await t.expect(Selector(".nav-settings").visible).ok();
+  await t.navigateTo("/settings");
   await t
-    .click(Selector(".nav-settings"))
     .expect(Selector(".input-language input", { timeout: 8000 }).visible)
     .ok()
     .expect(Selector("div.p-page-photos").visible)
     .notOk()
-    .click(Selector("#tab-settings-library"))
+    .navigateTo("/settings/library")
     .expect(Selector("form.p-form-settings").visible)
     .ok()
     .expect(Selector("div.p-page-photos").visible)
     .notOk()
-    .click(Selector("#tab-settings-advanced"))
+    .navigateTo("/settings/advanced")
     .expect(Selector("label").withText("Read-Only Mode").visible)
     .ok()
     .expect(Selector("div.p-page-photos").visible)
     .notOk()
-    .click(Selector("#tab-settings-sync"))
+    .navigateTo("/settings/sync")
     .expect(Selector("div.p-accounts-list").visible)
     .ok()
     .expect(Selector("div.p-page-photos").visible)
@@ -44,8 +44,8 @@ test.meta("testID", "admin-role-002")("Access to archive", async (t) => {
   const PhotoCountBrowse = await Selector("div.is-photo", { timeout: 5000 }).count;
   await page.openNav();
   await t.click(Selector(".nav-browse + div")).expect(Selector(".nav-archive").visible).ok();
+  await t.navigateTo("/archive");
   await t
-    .click(Selector(".nav-archive"))
     .expect(Selector("div.is-photo").withAttribute("data-uid", "pqnahct2mvee8sr4").visible)
     .ok();
   const PhotoCountArchive = await Selector("div.is-photo", { timeout: 5000 }).count;
@@ -57,8 +57,8 @@ test.meta("testID", "admin-role-003")("Access to review", async (t) => {
   const PhotoCountBrowse = await Selector("div.is-photo", { timeout: 5000 }).count;
   await page.openNav();
   await t.click(Selector(".nav-browse + div")).expect(Selector(".nav-review").visible).ok();
+  await t.navigateTo("/review");
   await t
-    .click(Selector(".nav-review"))
     .expect(Selector("div.is-photo").withAttribute("data-uid", "pqzuein2pdcg1kc7").visible)
     .ok();
   const PhotoCountReview = await Selector("div.is-photo", { timeout: 5000 }).count;
@@ -70,8 +70,8 @@ test.meta("testID", "admin-role-004")("Access to private", async (t) => {
   const PhotoCountBrowse = await Selector("div.is-photo", { timeout: 5000 }).count;
   await page.openNav();
   await t.expect(Selector(".nav-private").visible).ok();
+  await t.navigateTo("/private");
   await t
-    .click(Selector(".nav-private"))
     .expect(Selector("div.is-photo").withAttribute("data-uid", "pqmxlquf9tbc8mk2").visible)
     .ok();
   const PhotoCountPrivate = await Selector("div.is-photo", { timeout: 5000 }).count;
@@ -83,18 +83,18 @@ test.meta("testID", "admin-role-005")("Access to library", async (t) => {
   const PhotoCountBrowse = await Selector("div.is-photo", { timeout: 5000 }).count;
   await page.openNav();
   await t.expect(Selector(".nav-library").visible).ok();
+  await t.navigateTo("/library");
   await t
-    .click(Selector(".nav-library"))
     .expect(Selector(".input-index-folder input").visible)
     .ok()
     .expect(Selector("div.p-page-photos").visible)
     .notOk()
-    .click(Selector("#tab-library-import"))
+    .navigateTo("/library/import")
     .expect(Selector(".input-import-folder input").visible)
     .ok()
     .expect(Selector("div.p-page-photos").visible)
     .notOk()
-    .click(Selector("#tab-library-logs"))
+    .navigateTo("/library/logs")
     .expect(Selector("p.p-log-debug").visible)
     .ok()
     .expect(Selector("div.p-page-photos").visible)
@@ -102,21 +102,21 @@ test.meta("testID", "admin-role-005")("Access to library", async (t) => {
     .click(Selector(".nav-library + div"))
     .expect(Selector(".nav-originals").visible)
     .ok()
-    .click(Selector(".nav-originals"))
+    .navigateTo("/library/files")
     .expect(Selector("div.p-page-files").visible)
     .ok()
     .expect(Selector("div.p-page-photos").visible)
     .notOk()
     .expect(Selector(".nav-hidden").visible)
     .ok()
-    .click(Selector(".nav-hidden"));
+    .navigateTo("/library/hidden");
   const PhotoCountHidden = await Selector("div.is-photo", { timeout: 5000 }).count;
   await t
     .expect(PhotoCountBrowse)
     .gte(PhotoCountHidden)
     .expect(Selector(".nav-errors").visible)
     .ok()
-    .click(Selector(".nav-errors"))
+    .navigateTo("/library/errors")
     .expect(Selector("div.p-page-errors").visible)
     .ok()
     .expect(Selector("div.p-page-photos").visible)

+ 534 - 0
frontend/tests/acceptance-private/member-role.js

@@ -0,0 +1,534 @@
+import { Selector } from "testcafe";
+import testcafeconfig from "../acceptance/testcafeconfig";
+import Page from "../acceptance/page-model";
+
+fixture`Test member role`.page`${testcafeconfig.url}`;
+
+const page = new Page();
+
+test.meta("testID", "member-role-001")("No access to settings", async (t) => {
+  await page.login("member", "passwdmember");
+  await page.openNav();
+  await t.expect(Selector(".nav-settings").visible).notOk();
+  await t.navigateTo("/settings");
+  await t
+    .expect(Selector(".input-language input", { timeout: 8000 }).visible)
+    .notOk()
+    .expect(Selector("div.p-page-photos").visible)
+    .ok()
+    .navigateTo("/settings/library")
+    .expect(Selector("form.p-form-settings").visible)
+    .notOk()
+    .expect(Selector("div.p-page-photos").visible)
+    .ok()
+    .navigateTo("/settings/advanced")
+    .expect(Selector("label").withText("Read-Only Mode").visible)
+    .notOk()
+    .expect(Selector("div.p-page-photos").visible)
+    .ok()
+    .navigateTo("/settings/sync")
+    .expect(Selector("div.p-accounts-list").visible)
+    .notOk()
+    .expect(Selector("div.p-page-photos").visible)
+    .ok();
+});
+
+test.meta("testID", "member-role-002")("No access to archive", async (t) => {
+  await page.login("member", "passwdmember");
+  const PhotoCountBrowse = await Selector("div.is-photo", { timeout: 5000 }).count;
+  await page.openNav();
+  await t.click(Selector(".nav-browse + div")).expect(Selector(".nav-archive").visible).notOk();
+  await t.navigateTo("/archive");
+  await t
+    .expect(Selector("div.is-photo").withAttribute("data-uid", "pqnahct2mvee8sr4").visible)
+    .notOk();
+  const PhotoCountArchive = await Selector("div.is-photo", { timeout: 5000 }).count;
+  await t.expect(PhotoCountBrowse).eql(PhotoCountArchive);
+});
+
+test.meta("testID", "member-role-003")("No access to review", async (t) => {
+  await page.login("member", "passwdmember");
+  const PhotoCountBrowse = await Selector("div.is-photo", { timeout: 5000 }).count;
+  await page.openNav();
+  await t.click(Selector(".nav-browse + div")).expect(Selector(".nav-review").visible).notOk();
+  await t.navigateTo("/review");
+  await t
+    .expect(Selector("div.is-photo").withAttribute("data-uid", "pqzuein2pdcg1kc7").visible)
+    .notOk();
+  const PhotoCountReview = await Selector("div.is-photo", { timeout: 5000 }).count;
+  await t.expect(PhotoCountBrowse).eql(PhotoCountReview);
+});
+
+test.meta("testID", "member-role-004")("No access to private", async (t) => {
+  await page.login("member", "passwdmember");
+  const PhotoCountBrowse = await Selector("div.is-photo", { timeout: 5000 }).count;
+  await page.openNav();
+  await t.expect(Selector(".nav-private").visible).notOk();
+  await t.navigateTo("/private");
+  await t
+    .expect(Selector("div.is-photo").withAttribute("data-uid", "pqmxlquf9tbc8mk2").visible)
+    .notOk();
+  const PhotoCountPrivate = await Selector("div.is-photo", { timeout: 5000 }).count;
+  await t.expect(PhotoCountBrowse).eql(PhotoCountPrivate);
+});
+
+test.meta("testID", "member-role-005")("No access to library", async (t) => {
+  await page.login("member", "passwdmember");
+  const PhotoCountBrowse = await Selector("div.is-photo", { timeout: 5000 }).count;
+  await page.openNav();
+  await t.expect(Selector(".nav-library").visible).notOk();
+  await t.navigateTo("/library");
+  await t
+    .expect(Selector(".input-index-folder input").visible)
+    .notOk()
+    .expect(Selector("div.p-page-photos").visible)
+    .ok()
+    .navigateTo("/library/import")
+    .expect(Selector(".input-import-folder input").visible)
+    .notOk()
+    .expect(Selector("div.p-page-photos").visible)
+    .ok()
+    .navigateTo("/library/logs")
+    .expect(Selector("p.p-log-debug").visible)
+    .notOk()
+    .expect(Selector("div.p-page-photos").visible)
+    .ok()
+    .navigateTo("/library/files")
+    .expect(Selector(".nav-originals").visible)
+    .notOk()
+    .expect(Selector("div.p-page-files").visible)
+    .notOk()
+    .expect(Selector("div.p-page-photos").visible)
+    .ok()
+    .navigateTo("/library/hidden")
+    .expect(Selector(".nav-hidden").visible)
+    .notOk();
+  const PhotoCountHidden = await Selector("div.is-photo", { timeout: 5000 }).count;
+  await t
+    .expect(PhotoCountBrowse)
+    .eql(PhotoCountHidden)
+    .navigateTo("/library/errors")
+    .expect(Selector(".nav-errors").visible)
+    .notOk()
+    .expect(Selector("div.p-page-errors").visible)
+    .notOk()
+    .expect(Selector("div.p-page-photos").visible)
+    .ok();
+});
+
+test.meta("testID", "member-role-006")(
+  "No private/archived photos in search results",
+  async (t) => {
+    await page.login("member", "passwdmember");
+    const PhotoCountBrowse = await Selector("div.is-photo", { timeout: 5000 }).count;
+    await page.search("private:true");
+    const PhotoCountPrivate = await Selector("div.is-photo", { timeout: 5000 }).count;
+    await t.expect(PhotoCountPrivate).eql(0);
+    await t
+      .expect(Selector("div.is-photo").withAttribute("data-uid", "pqmxlquf9tbc8mk2").visible)
+      .notOk();
+    await page.search("archived:true");
+    const PhotoCountArchive = await Selector("div.is-photo", { timeout: 5000 }).count;
+    await t.expect(PhotoCountArchive).eql(0);
+    await t
+      .expect(Selector("div.is-photo").withAttribute("data-uid", "pqnahct2mvee8sr4").visible)
+      .notOk();
+    await page.search("quality:0");
+    const PhotoCountReview = await Selector("div.is-photo", { timeout: 5000 }).count;
+    await t.expect(PhotoCountReview).gte(PhotoCountBrowse);
+    await t
+      .expect(Selector("div.is-photo").withAttribute("data-uid", "pqzuein2pdcg1kc7").visible)
+      .ok();
+  }
+);
+
+test.meta("testID", "member-role-007")("No upload functionality", async (t) => {
+  await page.login("member", "passwdmember");
+  await t
+    .expect(Selector("button.action-upload").visible)
+    .notOk()
+    .click(Selector(".nav-albums"))
+    .expect(Selector("a.is-album").visible)
+    .ok()
+    .expect(Selector("button.action-upload").visible)
+    .notOk()
+    .click(Selector("a.is-album").nth(0))
+    .expect(Selector("div.is-photo").visible)
+    .ok()
+    .expect(Selector("button.action-upload").visible)
+    .notOk()
+    .click(Selector(".nav-video"))
+    .expect(Selector("button.action-upload").visible)
+    .notOk()
+    .click(Selector(".nav-favorites"))
+    .expect(Selector("button.action-upload").visible)
+    .notOk()
+    .click(Selector(".nav-moments"))
+    .expect(Selector("a.is-album").visible)
+    .ok()
+    .expect(Selector("button.action-upload").visible)
+    .notOk()
+    .click(Selector("a.is-album").nth(0))
+    .expect(Selector("div.is-photo").visible)
+    .ok()
+    .expect(Selector("button.action-upload").visible)
+    .notOk()
+    .click(Selector(".nav-calendar"))
+    .expect(Selector("a.is-album").visible)
+    .ok()
+    .expect(Selector("button.action-upload").visible)
+    .notOk()
+    .click(Selector("a.is-album").nth(0))
+    .expect(Selector("div.is-photo").visible)
+    .ok()
+    .expect(Selector("button.action-upload").visible)
+    .notOk();
+  await page.openNav();
+  await t
+    .click(Selector(".nav-places + div"))
+    .click(Selector(".nav-states"))
+    .expect(Selector("a.is-album").visible)
+    .ok()
+    .expect(Selector("button.action-upload").visible)
+    .notOk()
+    .click(Selector("a.is-album").nth(0))
+    .expect(Selector("div.is-photo").visible)
+    .ok()
+    .expect(Selector("button.action-upload").visible)
+    .notOk()
+    .click(Selector(".nav-folders"))
+    .expect(Selector("a.is-album").visible)
+    .ok()
+    .expect(Selector("button.action-upload").visible)
+    .notOk()
+    .click(Selector("a.is-album").nth(0))
+    .expect(Selector("div.is-photo").visible)
+    .ok()
+    .expect(Selector("button.action-upload").visible)
+    .notOk();
+});
+
+test.meta("testID", "member-role-008")("Member cannot like photos", async (t) => {
+  await page.login("member", "passwdmember");
+  const FirstPhoto = await Selector("div.is-photo.type-image").nth(0).getAttribute("data-uid");
+  const SecondPhoto = await Selector("div.is-photo").nth(1).getAttribute("data-uid");
+  await page.openNav();
+  await t.click(Selector(".nav-favorites"));
+  await t
+    .expect(Selector("div").withAttribute("data-uid", FirstPhoto).exists, { timeout: 5000 })
+    .notOk()
+    .expect(Selector("div").withAttribute("data-uid", SecondPhoto).exists, { timeout: 5000 })
+    .notOk();
+  await page.openNav();
+  await t.click(Selector(".nav-browse"));
+  await t.hover(Selector("div").withAttribute("data-uid", FirstPhoto));
+  if (await Selector(`.uid-${FirstPhoto} .action-fullscreen`).visible) {
+    await t.click(Selector(`.uid-${FirstPhoto} .action-fullscreen`));
+  } else {
+    await t.click(Selector("div").withAttribute("data-uid", FirstPhoto));
+  }
+  await t
+    .expect(Selector("#photo-viewer").visible)
+    .ok()
+    .expect(Selector('button[title="Like"]').exists)
+    .notOk()
+    .click(Selector('button[title="Close"]'))
+    .click(Selector(".p-expand-search"));
+  await page.setFilter("view", "Cards");
+  //await t.expect(Selector(`.uid-${FirstPhoto} .input-favorite`).hasAttribute("disabled")).ok();
+  await page.toggleLike(FirstPhoto);
+  await page.selectPhotoFromUID(SecondPhoto);
+  await page.editSelected();
+  await t
+    .click("#tab-info")
+    .expect(Selector(".input-favorite input").hasAttribute("disabled"))
+    .ok();
+  await page.turnSwitchOn("favorite");
+  await t.click(Selector(".action-close"));
+  await page.openNav();
+  await t.click(Selector(".nav-favorites"));
+  await t
+    .expect(Selector("div").withAttribute("data-uid", FirstPhoto).exists, { timeout: 5000 })
+    .notOk()
+    .expect(Selector("div").withAttribute("data-uid", SecondPhoto).exists, { timeout: 5000 })
+    .notOk();
+  await page.openNav();
+  await t.click(Selector(".nav-browse"));
+  await page.setFilter("view", "Mosaic");
+  //await t.expect(Selector(`.uid-${FirstPhoto} .input-favorite`).hasAttribute("disabled")).ok();
+  await page.toggleLike(FirstPhoto);
+  await page.setFilter("view", "List");
+  await t
+    .expect(Selector(`button.input-like`).hasAttribute("disabled"))
+    .ok()
+    .click(Selector(`button.input-like`));
+  await page.openNav();
+  await t.click(Selector(".nav-favorites"));
+  await t
+    .expect(Selector("div").withAttribute("data-uid", FirstPhoto).exists, { timeout: 5000 })
+    .notOk()
+    .expect(Selector("div").withAttribute("data-uid", SecondPhoto).exists, { timeout: 5000 })
+    .notOk();
+  await t
+    .click(Selector(".nav-albums"))
+    .click(Selector("a.is-album").nth(0))
+    .click(Selector('button[title="Toggle View"]'))
+    .expect(Selector(`button.input-like`).hasAttribute("disabled"))
+    .ok();
+});
+
+//TODO add to admin test as well
+test.meta("testID", "member-role-009")(
+  "Member cannot private, archive, share, add/remove to album",
+  async (t) => {
+    await page.login("member", "passwdmember");
+    const FirstPhoto = await Selector("div.is-photo.type-image").nth(0).getAttribute("data-uid");
+    const SecondPhoto = await Selector("div.is-photo").nth(1).getAttribute("data-uid");
+    await page.selectPhotoFromUID(FirstPhoto);
+    await t
+      .click(Selector("button.action-menu"))
+      .expect(Selector("button.action-private").visible)
+      .notOk()
+      .expect(Selector("button.action-archive").visible)
+      .notOk()
+      .expect(Selector("button.action-share").visible)
+      .notOk()
+      .expect(Selector("button.action-album").visible)
+      .notOk();
+    await page.clearSelection();
+    await page.setFilter("view", "List");
+    await t.expect(Selector(`button.input-private`).hasAttribute("disabled")).ok();
+    await t
+      .click(Selector(".nav-albums"))
+      .click(Selector("a.is-album").nth(0))
+      .expect(Selector("button.action-share").visible)
+      .notOk();
+    await page.toggleSelectNthPhoto(0);
+    await t
+      .click(Selector("button.action-menu"))
+      .expect(Selector("button.action-private").visible)
+      .notOk()
+      .expect(Selector("button.action-archive").visible)
+      .notOk()
+      .expect(Selector("button.action-share").visible)
+      .notOk()
+      .expect(Selector("button.action-album").visible)
+      .notOk()
+      .expect(Selector("button.action-remove").visible)
+      .notOk();
+    await page.clearSelection();
+    await t
+      .click(Selector('button[title="Toggle View"]'))
+      .expect(Selector(`button.input-private`).hasAttribute("disabled"))
+      .ok();
+  }
+);
+
+test.meta("testID", "member-role-010")("Member cannot approve low quality photos", async (t) => {
+  await page.login("member", "passwdmember");
+  await page.search('quality:0 name:"photos-013_1"');
+  await page.toggleSelectNthPhoto(0);
+  await page.editSelected();
+  await t.expect(Selector("button.action-approve").visible).notOk();
+});
+
+//TODO Admin version
+test.meta("testID", "member-role-011")("Edit dialog is read only for member", async (t) => {
+  await page.login("member", "passwdmember");
+  await page.search("faces:new");
+  //details
+  const FirstPhoto = await Selector("div.is-photo.type-image").nth(0).getAttribute("data-uid");
+  await page.selectPhotoFromUID(FirstPhoto);
+  await page.editSelected();
+  await t
+    .expect(Selector(".input-title input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-local-time input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-utc-time input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-day input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-month input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-year input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-timezone input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-latitude input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-longitude input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-altitude input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-country input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-camera input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-iso input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-exposure input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-lens input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-fnumber input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-focal-length input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-subject textarea").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-artist input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-copyright input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-license textarea").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-description textarea").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-keywords textarea").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-notes textarea").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector("button.action-apply").visible)
+    .notOk()
+    .expect(Selector("button.action-done").visible)
+    .notOk();
+  //labels
+  await t
+    .click(Selector("#tab-labels"))
+    .expect(Selector("button.action-remove").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-label input").exists)
+    .notOk()
+    .expect(Selector("button.p-photo-label-add").exists)
+    .notOk()
+    .click(Selector("div.p-inline-edit"))
+    .expect(Selector(".input-rename input").exists)
+    .notOk();
+  //people
+  await t
+    .click(Selector("#tab-people"))
+    .expect(Selector(".input-name input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector("button.input-reject").exists)
+    .notOk();
+  //info
+  await t
+    .click(Selector("#tab-info"))
+    .expect(Selector(".input-favorite input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-private input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-scan input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-panorama input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-stackable input").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector(".input-type input").hasAttribute("disabled"))
+    .ok();
+});
+
+//TODO admin version
+//TODO Already shared albums are visible
+test.meta("testID", "member-role-012")("No edit album functionality", async (t) => {
+  await page.login("member", "passwdmember");
+  await t.click(Selector(".nav-albums"));
+  await page.checkMemberAlbumRights();
+});
+
+test.meta("testID", "member-role-013")("No edit moment functionality", async (t) => {
+  await page.login("member", "passwdmember");
+  await t.click(Selector(".nav-moments"));
+  await page.checkMemberAlbumRights();
+});
+
+test.meta("testID", "member-role-014")("No edit state functionality", async (t) => {
+  await page.login("member", "passwdmember");
+  await page.openNav();
+  await t.click(Selector(".nav-places + div")).click(Selector(".nav-states"));
+  await page.checkMemberAlbumRights();
+});
+
+test.meta("testID", "member-role-015")("No edit calendar functionality", async (t) => {
+  await page.login("member", "passwdmember");
+  await page.openNav();
+  await t.click(Selector(".nav-calendar"));
+  await page.checkMemberAlbumRights();
+});
+
+test.meta("testID", "member-role-016")("No edit folder functionality", async (t) => {
+  await page.login("member", "passwdmember");
+  await page.openNav();
+  await t.click(Selector(".nav-folders"));
+  await page.checkMemberAlbumRights();
+});
+
+//TODO admin version
+//TODO improve star testing
+test.meta("testID", "member-role-017")("No edit labels functionality", async (t) => {
+  await page.login("member", "passwdmember");
+  await page.openNav();
+  await t.click(Selector(".nav-labels"));
+  const FirstLabel = await Selector("a.is-label").nth(0).getAttribute("data-uid");
+  await t
+    .click(Selector(`a.uid-${FirstLabel} div.inline-edit`))
+    .expect(Selector(".input-rename input").visible)
+    .notOk()
+    .expect(Selector(`a.uid-${FirstLabel} i.select-on`).visible)
+    .notOk()
+    .expect(Selector(`a.uid-${FirstLabel} i.select-off`).visible)
+    .ok()
+    .click(Selector(`.uid-${FirstLabel} .input-favorite`))
+    .expect(Selector(`a.uid-${FirstLabel} i.select-on`).visible)
+    .notOk()
+    .expect(Selector(`a.uid-${FirstLabel} i.select-off`).visible)
+    .ok();
+  await page.selectFromUID(FirstLabel);
+  await t
+    .click(Selector("button.action-menu"))
+    .expect(Selector("button.action-delete").visible)
+    .notOk()
+    .expect(Selector("button.action-album").visible)
+    .notOk();
+});
+
+test.meta("testID", "member-role-018")("No unstack, change primary actions", async (t) => {
+  await page.login("member", "passwdmember");
+  await page.search("stack:true");
+  //details
+  const FirstPhoto = await Selector("div.is-photo.type-image").nth(0).getAttribute("data-uid");
+  await page.selectPhotoFromUID(FirstPhoto);
+  await page.editSelected();
+  await t
+    .click(Selector("#tab-files"))
+    .expect(Selector("button.action-download").visible)
+    .ok()
+    .expect(Selector("button.action-download").hasAttribute("disabled"))
+    .notOk()
+    .click(Selector("li.v-expansion-panel__container").nth(1))
+    .expect(Selector("button.action-download").visible)
+    .ok()
+    .expect(Selector("button.action-download").hasAttribute("disabled"))
+    .notOk()
+    .expect(Selector("button.action-unstack").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector("button.action-primary").hasAttribute("disabled"))
+    .ok()
+    .expect(Selector("button.action-delete").hasAttribute("disabled"))
+    .ok();
+});
+
+//TODO existing people one normal + one hidden
+/*test.meta("testID", "member-role-009")("No edit people functionality", async (t) => {
+  //name on edit done
+  //rename fixture needed
+  //reject name fixture needed
+  //see new faces
+  //see hidden people fixture needed
+  //find hidden people fixture needed
+  //share
+  //add to album etc
+  //star
+});
+*/

+ 48 - 0
frontend/tests/acceptance/page-model.js

@@ -660,4 +660,52 @@ export default class Page {
       await t.click(Selector("button.action-done", { timeout: 5000 }));
     }
   }
+
+  async checkMemberAlbumRights() {
+    await t.expect(Selector("button.action-add").exists).notOk();
+    const FirstAlbum = await Selector("a.is-album").nth(0).getAttribute("data-uid");
+    await this.selectFromUID(FirstAlbum);
+    await t
+      .click(Selector("button.action-menu"))
+      .expect(Selector("button.action-edit").visible)
+      .notOk()
+      .expect(Selector("button.action-share").visible)
+      .notOk()
+      .expect(Selector("button.action-clone").visible)
+      .notOk()
+      .expect(Selector("button.action-delete").visible)
+      .notOk()
+      .expect(Selector("button.action-download").visible)
+      .ok();
+    await this.clearSelection();
+    await t
+      .click(Selector("button.action-title-edit"))
+      .expect(Selector(".input-title input").visible)
+      .notOk()
+      .expect(Selector(`a.uid-${FirstAlbum} i.select-on`).visible)
+      .notOk()
+      .expect(Selector(`a.uid-${FirstAlbum} i.select-off`).visible)
+      .ok()
+      .click(Selector(`.uid-${FirstAlbum} .input-favorite`))
+      .expect(Selector(`a.uid-${FirstAlbum} i.select-on`).visible)
+      .notOk()
+      .expect(Selector(`a.uid-${FirstAlbum} i.select-off`).visible)
+      .ok()
+      .click(Selector("a.is-album").nth(0))
+      .expect(Selector("button.action-share").visible)
+      .notOk()
+      .expect(Selector("button.action-edit").visible)
+      .notOk();
+    await this.toggleSelectNthPhoto(0);
+    await t
+      .click(Selector("button.action-menu"))
+      .expect(Selector("button.action-remove").visible)
+      .notOk()
+      .expect(Selector("button.action-album").visible)
+      .notOk()
+      .expect(Selector("button.action-private").visible)
+      .notOk()
+      .expect(Selector("button.action-share").visible)
+      .notOk();
+  }
 }

+ 153 - 134
frontend/tests/acceptance/photos.js

@@ -152,10 +152,9 @@ test.meta("testID", "photos-004")("Like/dislike photo/video", async (t) => {
   await page.toggleLike(FirstPhoto);
   await page.selectPhotoFromUID(SecondPhoto);
   await page.editSelected();
-    await page.turnSwitchOn("favorite");
-    await t
-        .click(Selector(".action-close"));
-    await t
+  await page.turnSwitchOn("favorite");
+  await t.click(Selector(".action-close"));
+  await t
     .expect(Selector("div.is-photo").withAttribute("data-uid", FirstPhoto).exists, {
       timeout: 5000,
     })
@@ -189,9 +188,8 @@ test.meta("testID", "photos-004")("Like/dislike photo/video", async (t) => {
   await page.toggleLike(FirstVideo);
   await page.toggleLike(FirstPhoto);
   await page.editSelected();
-    await page.turnSwitchOff("private");
-    await t
-    .click(Selector(".action-close"));
+  await page.turnSwitchOff("private");
+  await t.click(Selector(".action-close"));
   await page.clearSelection();
   if (t.browser.platform === "mobile") {
     await t.eval(() => location.reload());
@@ -207,8 +205,6 @@ test.meta("testID", "photos-004")("Like/dislike photo/video", async (t) => {
     .notOk();
 });
 
-
-
 test.meta("testID", "photos-007")("Edit photo/video", async (t) => {
   await page.openNav();
   await t.click(Selector(".nav-browse"));
@@ -264,26 +260,26 @@ test.meta("testID", "photos-007")("Edit photo/video", async (t) => {
     .expect(Selector(".input-title input").value)
     .eql(FirstPhotoTitle);
   await page.editPhoto(
-      "New Photo Title",
-      "Europe/Moscow",
-      "15",
-      "07",
-      "2019",
-      "04:30:30",
-      "-1",
-      "41.15333",
-      "20.168331",
-      "32",
-      "1/32",
-      "29",
-      "33",
-      "Super nice edited photo",
-      "Happy",
-      "Happy2020",
-      "Super nice cat license",
-      "Description of a nice image :)",
-      ", cat, love",
-      "Some notes"
+    "New Photo Title",
+    "Europe/Moscow",
+    "15",
+    "07",
+    "2019",
+    "04:30:30",
+    "-1",
+    "41.15333",
+    "20.168331",
+    "32",
+    "1/32",
+    "29",
+    "33",
+    "Super nice edited photo",
+    "Happy",
+    "Happy2020",
+    "Super nice cat license",
+    "Description of a nice image :)",
+    ", cat, love",
+    "Some notes"
   );
   if (t.browser.platform === "mobile") {
     await t.eval(() => location.reload());
@@ -295,33 +291,55 @@ test.meta("testID", "photos-007")("Edit photo/video", async (t) => {
     .eql("New Photo Title");
   await page.selectPhotoFromUID(FirstPhoto);
   await page.editSelected();
-  await page.checkEditFormValues("New Photo Title", "15", "07", "2019", "04:30:30",
-      "01:30:30", "Europe/Moscow", "Albania", "-1", "", "", "",
-      "32", "1/32", "",  "29", "33", "Super nice edited photo", "Happy",
-      "Happy2020", "Super nice cat license", "Description of a nice image :)", "cat", "");
+  await page.checkEditFormValues(
+    "New Photo Title",
+    "15",
+    "07",
+    "2019",
+    "04:30:30",
+    "01:30:30",
+    "Europe/Moscow",
+    "Albania",
+    "-1",
+    "",
+    "",
+    "",
+    "32",
+    "1/32",
+    "",
+    "29",
+    "33",
+    "Super nice edited photo",
+    "Happy",
+    "Happy2020",
+    "Super nice cat license",
+    "Description of a nice image :)",
+    "cat",
+    ""
+  );
   await page.undoPhotoEdit(
-      FirstPhotoTitle,
-      FirstPhotoTimezone,
-      FirstPhotoDay,
-      FirstPhotoMonth,
-      FirstPhotoYear,
-      FirstPhotoLocalTime,
-      FirstPhotoAltitude,
-      FirstPhotoLatitude,
-      FirstPhotoLongitude,
-      FirstPhotoCountry,
-      FirstPhotoIso,
-      FirstPhotoExposure,
-      FirstPhotoFnumber,
-      FirstPhotoFocalLength,
-      FirstPhotoSubject,
-      FirstPhotoArtist,
-      FirstPhotoCopyright,
-      FirstPhotoLicense,
-      FirstPhotoDescription,
-      FirstPhotoKeywords,
-      FirstPhotoNotes
-  )
+    FirstPhotoTitle,
+    FirstPhotoTimezone,
+    FirstPhotoDay,
+    FirstPhotoMonth,
+    FirstPhotoYear,
+    FirstPhotoLocalTime,
+    FirstPhotoAltitude,
+    FirstPhotoLatitude,
+    FirstPhotoLongitude,
+    FirstPhotoCountry,
+    FirstPhotoIso,
+    FirstPhotoExposure,
+    FirstPhotoFnumber,
+    FirstPhotoFocalLength,
+    FirstPhotoSubject,
+    FirstPhotoArtist,
+    FirstPhotoCopyright,
+    FirstPhotoLicense,
+    FirstPhotoDescription,
+    FirstPhotoKeywords,
+    FirstPhotoNotes
+  );
   const clipboardCount = await Selector("span.count-clipboard", { timeout: 5000 });
   await t
     .expect(clipboardCount.textContent)
@@ -395,7 +413,7 @@ test.meta("testID", "photos-010")("Ungroup files", async (t) => {
     .click(Selector("button.action-title-edit").withAttribute("data-uid", SequentialPhoto))
     .click(Selector("#tab-files"))
     .click(Selector("div.v-expansion-panel__header__icon").nth(0))
-      .click(Selector("div.v-expansion-panel__header__icon").nth(1))
+    .click(Selector("div.v-expansion-panel__header__icon").nth(1))
     .click(Selector(".action-unstack"))
     .wait(12000)
     .click(Selector("button.action-close"));
@@ -448,85 +466,86 @@ test.skip.meta("testID", "photos-011")("Delete non primary file", async (t) => {
 });
 
 test.meta("testID", "photos-012")("Mark photos/videos as panorama/scan", async (t) => {
+  await page.openNav();
+  await page.search("photo:true");
+  const FirstPhoto = await Selector("div.is-photo.type-image").nth(0).getAttribute("data-uid");
+  await page.search("video:true");
+  const FirstVideo = await Selector("div.is-photo").nth(1).getAttribute("data-uid");
+  await page.openNav();
+  await t
+    .click(Selector(".nav-browse + div"))
+    .click(Selector(".nav-scans"))
+    .expect(Selector("div").withAttribute("data-uid", FirstPhoto).exists, { timeout: 5000 })
+    .notOk()
+    .expect(Selector("div").withAttribute("data-uid", FirstVideo).exists, { timeout: 5000 })
+    .notOk();
+  if (t.browser.platform === "mobile") {
     await page.openNav();
-    await page.search("photo:true");
-    const FirstPhoto = await Selector("div.is-photo.type-image").nth(0).getAttribute("data-uid");
-    await page.search("video:true");
-    const FirstVideo = await Selector("div.is-photo").nth(1).getAttribute("data-uid");
+  }
+  await t
+    .click(Selector(".nav-panoramas"))
+    .expect(Selector("div").withAttribute("data-uid", FirstPhoto).exists, { timeout: 5000 })
+    .notOk()
+    .expect(Selector("div").withAttribute("data-uid", FirstVideo).exists, { timeout: 5000 })
+    .notOk();
+  await page.openNav();
+  await t.click(Selector(".nav-browse"));
+  await page.selectPhotoFromUID(FirstPhoto);
+  await page.editSelected();
+  await page.turnSwitchOn("scan");
+  await page.turnSwitchOn("panorama");
+  await t.click(Selector(".action-close"));
+  await page.clearSelection();
+  await page.selectPhotoFromUID(FirstVideo);
+  await page.editSelected();
+  await page.turnSwitchOn("panorama");
+  await t.click(Selector(".action-close"));
+  await page.clearSelection();
+  await t
+    .expect(Selector("div").withAttribute("data-uid", FirstPhoto).exists, { timeout: 5000 })
+    .ok()
+    .expect(Selector("div").withAttribute("data-uid", FirstVideo).exists, { timeout: 5000 })
+    .ok();
+  if (t.browser.platform === "mobile") {
     await page.openNav();
-    await t.click(Selector(".nav-browse + div"))
-        .click(Selector(".nav-scans"))
-        .expect(Selector("div").withAttribute("data-uid", FirstPhoto).exists, { timeout: 5000 })
-        .notOk()
-        .expect(Selector("div").withAttribute("data-uid", FirstVideo).exists, { timeout: 5000 })
-        .notOk();
-    if (t.browser.platform === "mobile") {
-        await page.openNav();
-    }
-    await t
-        .click(Selector(".nav-panoramas"))
-        .expect(Selector("div").withAttribute("data-uid", FirstPhoto).exists, { timeout: 5000 })
-        .notOk()
-        .expect(Selector("div").withAttribute("data-uid", FirstVideo).exists, { timeout: 5000 })
-        .notOk();
+  }
+  await t
+    .click(Selector(".nav-scans"))
+    .expect(Selector("div").withAttribute("data-uid", FirstPhoto).exists, { timeout: 5000 })
+    .ok();
+  if (t.browser.platform === "mobile") {
     await page.openNav();
-    await t.click(Selector(".nav-browse"));
-    await page.selectPhotoFromUID(FirstPhoto);
-    await page.editSelected();
-    await page.turnSwitchOn("scan");
-    await page.turnSwitchOn("panorama");
-    await t.click(Selector(".action-close"));
-    await page.clearSelection();
-    await page.selectPhotoFromUID(FirstVideo);
-    await page.editSelected();
-    await page.turnSwitchOn("panorama");
-    await t.click(Selector(".action-close"));
-    await page.clearSelection();
-    await t.expect(Selector("div").withAttribute("data-uid", FirstPhoto).exists, { timeout: 5000 })
-        .ok()
-        .expect(Selector("div").withAttribute("data-uid", FirstVideo).exists, { timeout: 5000 })
-        .ok();
-    if (t.browser.platform === "mobile") {
-        await page.openNav();
-    }
-    await t
-        .click(Selector(".nav-scans"))
-        .expect(Selector("div").withAttribute("data-uid", FirstPhoto).exists, { timeout: 5000 })
-        .ok();
-    if (t.browser.platform === "mobile") {
-        await page.openNav();
-    }
-    await t
-        .click(Selector(".nav-panoramas"))
-    await page.selectPhotoFromUID(FirstPhoto);
-    await page.editSelected();
-    await page.turnSwitchOff("panorama");
-    await page.turnSwitchOff("scan");
-    await t.click(Selector(".action-close"));
-    await page.clearSelection();
-    await page.selectPhotoFromUID(FirstVideo);
-    await page.editSelected();
-    await page.turnSwitchOff("panorama");
-    await t.click(Selector(".action-close"));
-    await page.clearSelection();
-    if (t.browser.platform === "mobile") {
-        await t.eval(() => location.reload());
-    } else {
-        await t.click(Selector("button.action-reload"));
-    }
-    await t.expect(Selector("div").withAttribute("data-uid", FirstPhoto).exists, { timeout: 5000 })
-        .notOk()
-        .expect(Selector("div").withAttribute("data-uid", FirstVideo).exists, { timeout: 5000 })
-        .notOk();
-    if (t.browser.platform === "mobile") {
-        await page.openNav();
-        await t.click(Selector(".nav-browse + div"));
-    }
-    await t
-        .click(Selector(".nav-scans"))
-        .expect(Selector("div").withAttribute("data-uid", FirstPhoto).exists, { timeout: 5000 })
-        .notOk()
-        .expect(Selector("div").withAttribute("data-uid", FirstVideo).exists, { timeout: 5000 })
-        .notOk();
+  }
+  await t.click(Selector(".nav-panoramas"));
+  await page.selectPhotoFromUID(FirstPhoto);
+  await page.editSelected();
+  await page.turnSwitchOff("panorama");
+  await page.turnSwitchOff("scan");
+  await t.click(Selector(".action-close"));
+  await page.clearSelection();
+  await page.selectPhotoFromUID(FirstVideo);
+  await page.editSelected();
+  await page.turnSwitchOff("panorama");
+  await t.click(Selector(".action-close"));
+  await page.clearSelection();
+  if (t.browser.platform === "mobile") {
+    await t.eval(() => location.reload());
+  } else {
+    await t.click(Selector("button.action-reload"));
+  }
+  await t
+    .expect(Selector("div").withAttribute("data-uid", FirstPhoto).exists, { timeout: 5000 })
+    .notOk()
+    .expect(Selector("div").withAttribute("data-uid", FirstVideo).exists, { timeout: 5000 })
+    .notOk();
+  if (t.browser.platform === "mobile") {
+    await page.openNav();
+    await t.click(Selector(".nav-browse + div"));
+  }
+  await t
+    .click(Selector(".nav-scans"))
+    .expect(Selector("div").withAttribute("data-uid", FirstPhoto).exists, { timeout: 5000 })
+    .notOk()
+    .expect(Selector("div").withAttribute("data-uid", FirstVideo).exists, { timeout: 5000 })
+    .notOk();
 });
-