Просмотр исходного кода

Merge remote-tracking branch 'origin/master' into support/apply-nextjs-2

Yuki Takei 3 лет назад
Родитель
Сommit
8fbc17e1a5
32 измененных файлов с 362 добавлено и 113 удалено
  1. 16 1
      CHANGELOG.md
  2. 2 2
      packages/app/docker/README.md
  3. 5 1
      packages/app/public/static/locales/en_US/admin.json
  4. 1 0
      packages/app/public/static/locales/en_US/translation.json
  5. 7 3
      packages/app/public/static/locales/ja_JP/admin.json
  6. 1 0
      packages/app/public/static/locales/ja_JP/translation.json
  7. 5 1
      packages/app/public/static/locales/zh_CN/admin.json
  8. 1 0
      packages/app/public/static/locales/zh_CN/translation.json
  9. 4 5
      packages/app/src/components/Admin/Customize/CustomizeLogoSetting.tsx
  10. 6 1
      packages/app/src/components/Common/Dropdown/PageItemControl.tsx
  11. 54 13
      packages/app/src/components/Common/ImageCropModal.tsx
  12. 3 4
      packages/app/src/components/Me/ProfileImageSettings.tsx
  13. 15 13
      packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  14. 1 1
      packages/app/src/components/Page/TagLabels.tsx
  15. 17 17
      packages/app/src/components/PageEditor/EmojiPickerHelper.ts
  16. 1 0
      packages/app/src/components/PageList/PageListItemL.tsx
  17. 1 1
      packages/app/src/components/PrivateLegacyPages.tsx
  18. 1 1
      packages/app/src/components/SavePageControls.tsx
  19. 1 1
      packages/app/src/components/Sidebar/RecentChanges.tsx
  20. 1 1
      packages/app/src/interfaces/activity.ts
  21. 1 1
      packages/app/src/server/routes/index.js
  22. 15 1
      packages/app/src/server/service/installer.ts
  23. 9 0
      packages/app/test/cypress/integration/20-basic-features/access-to-page.spec.ts
  24. 11 4
      packages/app/test/cypress/integration/20-basic-features/access-to-pagelist.spec.ts
  25. 82 10
      packages/app/test/cypress/integration/20-basic-features/use-tools.spec.ts
  26. 14 10
      packages/app/test/cypress/integration/21-basic-features-for-guest/access-to-page.spec.ts
  27. 53 12
      packages/app/test/cypress/integration/30-search/search.spec.ts
  28. 2 0
      packages/app/test/cypress/integration/40-admin/access-to-admin-page.spec.ts
  29. 21 3
      packages/app/test/cypress/integration/50-sidebar/access-to-side-bar.spec.ts
  30. 8 3
      packages/app/test/cypress/integration/50-sidebar/switching-sidebar-mode.spec.ts
  31. 2 2
      packages/app/test/cypress/support/commands.ts
  32. 1 1
      packages/app/test/cypress/support/index.ts

+ 16 - 1
CHANGELOG.md

@@ -1,9 +1,24 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v5.1.4...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v5.1.5...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v5.1.5](https://github.com/weseek/growi/compare/v5.1.4...v5.1.5) - 2022-10-04
+
+### 💎 Features
+
+- feat: Add option to not use crop modal on brand logo upload (#6677) @Yohei-Shiina
+
+### 🚀 Improvement
+
+- imprv: Emoji picker performance for v5.x (#6689) @hakumizuki
+
+### 🐛 Bug Fixes
+
+- fix(auditlog): Attachment download is displayed even if the filter is unchecked (#6688) @miya
+- fix: firstName and lastName japanese translations in SAML  (#6631) @kaoritokashiki
+
 ## [v5.1.4](https://github.com/weseek/growi/compare/v5.1.3...v5.1.4) - 2022-09-12
 
 ### 💎 Features

+ 2 - 2
packages/app/docker/README.md

@@ -11,8 +11,8 @@ Supported tags and respective Dockerfile links
 ------------------------------------------------
 
 * [`6.0.0`, `6.0`, `6`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v6.0.0/packages/app/docker/Dockerfile)
-* [`5.1.4`, `5.1`, `5`](https://github.com/weseek/growi/blob/v5.1.4/packages/app/docker/Dockerfile)
-* [`5.1.4-nocdn`, `5.1-nocdn`, `5-nocdn`](https://github.com/weseek/growi/blob/v5.1.4/packages/app/docker/Dockerfile)
+* [`5.1.5`, `5.1`, `5`](https://github.com/weseek/growi/blob/v5.1.5/packages/app/docker/Dockerfile)
+* [`5.1.5-nocdn`, `5.1-nocdn`, `5-nocdn`](https://github.com/weseek/growi/blob/v5.1.5/packages/app/docker/Dockerfile)
 * [`4.5.23`, `4.5`, `4`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v4.5.23/packages/app/docker/Dockerfile)
 * [`4.5.23-nocdn`, `4.5-nocdn`, `4-nocdn`, `latest-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.5.23/packages/app/docker/Dockerfile)
 

+ 5 - 1
packages/app/public/static/locales/en_US/admin.json

@@ -907,6 +907,10 @@
     "PAGE_DELETE_COMPLETELY": "Delete completely page",
     "PAGE_REVERT": "Revert page",
     "PAGE_EMPTY_TRASH": "Empty trash",
+    "PAGE_RECURSIVELY_RENAME": "Recursive page rename",
+    "PAGE_RECURSIVELY_DELETE": "Recursive page delete",
+    "PAGE_RECURSIVELY_DELETE_COMPLETELY": "Recursive page delete completely",
+    "PAGE_RECURSIVELY_REVERT": "Recursive page revert",
     "PAGE_SUBSCRIBE": "Subscribe page",
     "PAGE_UNSUBSCRIBE": "Unsubscribe page",
     "PAGE_EXPORT": "Export page",
@@ -924,7 +928,7 @@
     "SHARE_LINK_NOT_FOUND": "Page view (Not found share link)",
     "ATTACHMENT_ADD": "Add Attachment",
     "ATTACHMENT_REMOVE": "Delete Attachment",
-    "ACTION_ATTACHMENT_DOWNLOAD": "Download Attachment",
+    "ATTACHMENT_DOWNLOAD": "Download Attachment",
     "SEARCH_PAGE": "Page Search",
     "SEARCH_PAGE_VIEW": "Page view(Search results page)",
     "ADMIN_APP_SETTING_UPDATE": "Update App Settings",

+ 1 - 0
packages/app/public/static/locales/en_US/translation.json

@@ -815,6 +815,7 @@
   "crop_image_modal": {
     "image_crop": "Image Crop",
     "crop": "Crop",
+    "save": "Save",
     "reset": "Reset",
     "cancel": "Cancel"
   },

+ 7 - 3
packages/app/public/static/locales/ja_JP/admin.json

@@ -225,8 +225,8 @@
       "attrMapId": "ID",
       "attrMapUsername": "ユーザー名",
       "attrMapMail": "メールアドレス",
-      "attrMapFirstName": "",
-      "attrMapLastName": "",
+      "attrMapFirstName": "",
+      "attrMapLastName": "",
       "ABLCRule": "ルール"
     }
   },
@@ -919,6 +919,10 @@
     "PAGE_DELETE_COMPLETELY": "ページの完全削除",
     "PAGE_REVERT": "ページを元に戻す",
     "PAGE_EMPTY_TRASH": "ゴミ箱を空にする",
+    "PAGE_RECURSIVELY_RENAME": "再帰的なページのリネーム",
+    "PAGE_RECURSIVELY_DELETE": "再帰的なページの削除",
+    "PAGE_RECURSIVELY_DELETE_COMPLETELY": "再起的なページの完全削除",
+    "PAGE_RECURSIVELY_REVERT": "再起的なページの復元",
     "PAGE_SUBSCRIBE": "ページをサブスクライブ",
     "PAGE_UNSUBSCRIBE": "ページをアンサブスクライブ",
     "PAGE_EXPORT": "マークダウン形式でページをエクスポート",
@@ -936,7 +940,7 @@
     "SHARE_LINK_NOT_FOUND": "ページ閲覧(存在しない共有リンク)",
     "ATTACHMENT_ADD": "添付データの追加",
     "ATTACHMENT_REMOVE": "添付データの削除",
-    "ACTION_ATTACHMENT_DOWNLOAD": "添付データのダウンロード",
+    "ATTACHMENT_DOWNLOAD": "添付データのダウンロード",
     "SEARCH_PAGE": "ページの検索",
     "SEARCH_PAGE_VIEW": "ページ閲覧(検索結果ページ)",
     "ADMIN_APP_SETTING_UPDATE": "アプリ設定の更新",

+ 1 - 0
packages/app/public/static/locales/ja_JP/translation.json

@@ -806,6 +806,7 @@
   "crop_image_modal": {
     "image_crop": "画像の切り抜き",
     "crop": "トリミング",
+    "save": "保存",
     "reset": "リセット",
     "cancel": "キャンセル"
   },

+ 5 - 1
packages/app/public/static/locales/zh_CN/admin.json

@@ -885,6 +885,10 @@
     "PAGE_DELETE_COMPLETELY": "彻底删除页面",
     "PAGE_REVERT": "还原页面",
     "PAGE_EMPTY_TRASH": "清空垃圾箱",
+    "PAGE_RECURSIVELY_RENAME": "递归页面重命名",
+    "PAGE_RECURSIVELY_DELETE": "递归页面删除",
+    "PAGE_RECURSIVELY_DELETE_COMPLETELY": "递归页面完全删除",
+    "PAGE_RECURSIVELY_REVERT": "递归页面还原",
     "PAGE_SUBSCRIBE": "订阅页面",
     "PAGE_UNSUBSCRIBE": "退订页面",
     "PAGE_EXPORT": "导出页面",
@@ -902,7 +906,7 @@
     "SHARE_LINK_NOT_FOUND": "页面浏览量(未找到分享链接)",
     "ATTACHMENT_ADD": "添加附件",
     "ATTACHMENT_REMOVE": "删除附件",
-    "ACTION_ATTACHMENT_DOWNLOAD": "下载附件",
+    "ATTACHMENT_DOWNLOAD": "下载附件",
     "SEARCH_PAGE": "页面搜索",
     "SEARCH_PAGE_VIEW": "页面浏览量(搜索结果页面)",
     "ADMIN_APP_SETTING_UPDATE": "更新应用设置",

+ 1 - 0
packages/app/public/static/locales/zh_CN/translation.json

@@ -862,6 +862,7 @@
   "crop_image_modal": {
     "image_crop": "图像裁剪",
     "crop": "修剪",
+    "save": "节省",
     "reset": "重启",
     "cancel": "取消"
   },

+ 4 - 5
packages/app/src/components/Admin/Customize/CustomizeLogoSetting.tsx

@@ -66,7 +66,6 @@ const CustomizeLogoSetting = (): JSX.Element => {
     }
   }, [t, isDefaultLogo, customizedLogoSrc]);
 
-
   const onClickDeleteBtn = useCallback(async() => {
     try {
       await apiv3Delete('/customize-setting/delete-brand-logo');
@@ -80,7 +79,8 @@ const CustomizeLogoSetting = (): JSX.Element => {
     }
   }, [t]);
 
-  const onCropCompleted = useCallback(async(croppedImage) => {
+
+  const processImageCompletedHandler = useCallback(async(croppedImage) => {
     try {
       const formData = new FormData();
       formData.append('file', croppedImage);
@@ -93,10 +93,8 @@ const CustomizeLogoSetting = (): JSX.Element => {
       setRetrieveError(err);
       throw new Error('Failed to upload brand logo');
     }
-    setIsImageCropModalShow(false);
   }, [t]);
 
-
   return (
     <React.Fragment>
       <div className="row">
@@ -172,8 +170,9 @@ const CustomizeLogoSetting = (): JSX.Element => {
         isShow={isImageCropModalShow}
         src={uploadLogoSrc}
         onModalClose={() => setIsImageCropModalShow(false)}
-        onCropCompleted={onCropCompleted}
+        onImageProcessCompleted={processImageCompletedHandler}
         isCircular={false}
+        showCropOption={false}
       />
     </React.Fragment>
   );

+ 6 - 1
packages/app/src/components/Common/Dropdown/PageItemControl.tsx

@@ -245,7 +245,12 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
   }
 
   return (
-    <DropdownMenu positionFixed modifiers={{ preventOverflow: { boundariesElement: undefined } }} right={alignRight}>
+    <DropdownMenu
+      data-testid="page-item-control-menu"
+      positionFixed
+      modifiers={{ preventOverflow: { boundariesElement: undefined } }}
+      right={alignRight}
+    >
       {contents}
     </DropdownMenu>
   );

+ 54 - 13
packages/app/src/components/Common/ImageCropModal.tsx

@@ -33,17 +33,19 @@ type Props = {
   isShow: boolean,
   src: string | ArrayBuffer | null,
   onModalClose: () => void,
-  onCropCompleted: (res: any) => void,
+  onImageProcessCompleted: (res: any) => void,
   isCircular: boolean,
+  showCropOption: boolean
 }
 const ImageCropModal: FC<Props> = (props: Props) => {
 
   const {
-    isShow, src, onModalClose, onCropCompleted, isCircular,
+    isShow, src, onModalClose, onImageProcessCompleted, isCircular, showCropOption,
   } = props;
 
-  const [imageRef, setImageRef] = useState<HTMLImageElement>();
+  const [imageRef, setImageRef] = useState<HTMLImageElement | null>(null);
   const [cropOptions, setCropOtions] = useState<CropOptions>(null);
+  const [isCropImage, setIsCropImage] = useState<boolean>(true);
   const { t } = useTranslation();
   const reset = useCallback(() => {
     if (imageRef) {
@@ -93,31 +95,70 @@ const ImageCropModal: FC<Props> = (props: Props) => {
     }
   };
 
-  const crop = async() => {
-    // crop immages
+  // Convert base64 Image to blob
+  const convertBase64ToBlob = async(base64Image: string) => {
+    const base64Response = await fetch(base64Image);
+    return base64Response.blob();
+  };
+
+
+  // Clear image and set isImageCrop true on modal close
+  const onModalCloseHandler = async() => {
+    setImageRef(null);
+    setIsCropImage(true);
+    onModalClose();
+  };
+
+  // Process and save image
+  // Cropping image is optional
+  // If crop is active , the saved image is cropped image (png). Otherwise, the original image will be saved (Original size and file type)
+  const processAndSaveImage = async() => {
     if (imageRef && cropOptions?.width && cropOptions.height) {
-      const result = await getCroppedImg(imageRef, cropOptions);
-      onCropCompleted(result);
+      const processedImage = isCropImage ? await getCroppedImg(imageRef, cropOptions) : await convertBase64ToBlob(imageRef.src);
+      // Save image to database
+      onImageProcessCompleted(processedImage);
     }
+    onModalCloseHandler();
   };
 
   return (
-    <Modal isOpen={isShow} toggle={onModalClose}>
-      <ModalHeader tag="h4" toggle={onModalClose} className="bg-info text-light">
+    <Modal isOpen={isShow} toggle={onModalCloseHandler}>
+      <ModalHeader tag="h4" toggle={onModalCloseHandler} className="bg-info text-light">
         {t('crop_image_modal.image_crop')}
       </ModalHeader>
       <ModalBody className="my-4">
-        <ReactCrop src={src} crop={cropOptions} onImageLoaded={onImageLoaded} onChange={onCropChange} circularCrop={isCircular} />
+        {
+          isCropImage
+            ? (<ReactCrop src={src} crop={cropOptions} onImageLoaded={onImageLoaded} onChange={onCropChange} circularCrop={isCircular} />)
+            : (<img style={{ maxWidth: imageRef?.width }} src={imageRef?.src} />)
+        }
       </ModalBody>
       <ModalFooter>
         <button type="button" className="btn btn-outline-danger rounded-pill mr-auto" onClick={reset}>
           {t('crop_image_modal.reset')}
         </button>
-        <button type="button" className="btn btn-outline-secondary rounded-pill mr-2" onClick={onModalClose}>
+        { !showCropOption && (
+          <div className="mr-auto">
+            <div className="custom-control custom-switch ">
+              <input
+                id="cropImageOption"
+                className="custom-control-input mr-auto"
+                type="checkbox"
+                checked={isCropImage}
+                onChange={() => { setIsCropImage(!isCropImage) }}
+              />
+              <label className="custom-control-label" htmlFor="cropImageOption">
+                { t('crop_image_modal.image_crop') }
+              </label>
+            </div>
+          </div>
+        )
+        }
+        <button type="button" className="btn btn-outline-secondary rounded-pill mr-2" onClick={onModalCloseHandler}>
           {t('crop_image_modal.cancel')}
         </button>
-        <button type="button" className="btn btn-outline-primary rounded-pill" onClick={crop}>
-          {t('crop_image_modal.crop')}
+        <button type="button" className="btn btn-outline-primary rounded-pill" onClick={processAndSaveImage}>
+          { isCropImage ? t('crop_image_modal.crop') : t('crop_image_modal.save') }
         </button>
       </ModalFooter>
     </Modal>

+ 3 - 4
packages/app/src/components/Me/ProfileImageSettings.tsx

@@ -42,7 +42,7 @@ const ProfileImageSettings = (): JSX.Element => {
     setShowImageCropModal(true);
   }, []);
 
-  const cropCompletedHandler = useCallback(async(croppedImage) => {
+  const processImageCompletedHandler = useCallback(async(croppedImage) => {
     try {
       const formData = new FormData();
       formData.append('file', croppedImage);
@@ -53,8 +53,6 @@ const ProfileImageSettings = (): JSX.Element => {
       // eslint-disable-next-line @typescript-eslint/no-explicit-any
       setUploadedPictureSrc((response as any).attachment.filePathProxied);
 
-      // close modal
-      setShowImageCropModal(false);
     }
     catch (err) {
       toastError(err);
@@ -158,8 +156,9 @@ const ProfileImageSettings = (): JSX.Element => {
         isShow={showImageCropModal}
         src={imageCropSrc}
         onModalClose={() => setShowImageCropModal(false)}
-        onCropCompleted={cropCompletedHandler}
+        onImageProcessCompleted={processImageCompletedHandler}
         isCircular
+        showCropOption
       />
 
       <div className="row my-3">

+ 15 - 13
packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -385,19 +385,21 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
     : currentPage?.path;
 
   return (
-    <GrowiSubNavigation
-      pagePath={pagePath}
-      pageId={currentPage?._id}
-      showDrawerToggler={isDrawerMode}
-      showTagLabel={isAbleToShowTagLabel}
-      isGuestUser={isGuestUser}
-      isDrawerMode={isDrawerMode}
-      isCompactMode={isCompactMode}
-      tags={isViewMode ? tagsInfoData?.tags : tagsForEditors}
-      tagsUpdatedHandler={isViewMode ? tagsUpdatedHandlerForViewMode : tagsUpdatedHandlerForEditMode}
-      rightComponent={RightComponent}
-      additionalClasses={['container-fluid']}
-    />
+    <div data-testid="grw-contextual-sub-nav">
+      <GrowiSubNavigation
+        pagePath={pagePath}
+        pageId={currentPage?._id}
+        showDrawerToggler={isDrawerMode}
+        showTagLabel={isAbleToShowTagLabel}
+        isGuestUser={isGuestUser}
+        isDrawerMode={isDrawerMode}
+        isCompactMode={isCompactMode}
+        tags={isViewMode ? tagsInfoData?.tags : tagsForEditors}
+        tagsUpdatedHandler={isViewMode ? tagsUpdatedHandlerForViewMode : tagsUpdatedHandlerForEditMode}
+        rightComponent={RightComponent}
+        additionalClasses={['container-fluid']}
+      />
+    </div>
   );
 };
 

+ 1 - 1
packages/app/src/components/Page/TagLabels.tsx

@@ -36,7 +36,7 @@ export const TagLabels:FC<Props> = (props: Props) => {
 
   return (
     <>
-      <div className={`${styles['grw-tag-labels']} grw-tag-labels d-flex align-items-center`}>
+      <div className={`${styles['grw-tag-labels']} grw-tag-labels d-flex align-items-center`} data-testid="grw-tag-labels">
         <i className="tag-icon icon-tag mr-2"></i>
         <RenderTagLabels
           tags={tags}

+ 17 - 17
packages/app/src/components/PageEditor/EmojiPickerHelper.ts

@@ -16,24 +16,24 @@ export default class EmojiPickerHelper {
     this.editor = editor;
   }
 
-  setStyle = (): CSSProperties => {
-    const offset = 20;
-    const emojiPickerHeight = 420;
-    const cursorPos = this.editor.cursorCoords(true);
-    const editorPos = this.editor.getWrapperElement().getBoundingClientRect();
-    // Emoji Picker bottom position exceed editor's bottom position
-    if (cursorPos.bottom + emojiPickerHeight > editorPos.bottom) {
+    setStyle = ():  CSSProperties => {
+      const offset = 20;
+      const emojiPickerHeight = 420;
+      const cursorPos = this.editor.cursorCoords(true);
+      const editorPos = this.editor.getWrapperElement().getBoundingClientRect();
+      // Emoji Picker bottom position exceed editor's bottom position
+      if (cursorPos.bottom + emojiPickerHeight > editorPos.bottom) {
+        return {
+          top: editorPos.bottom - emojiPickerHeight,
+          left: cursorPos.left + offset,
+          position: 'fixed',
+        };
+      }
       return {
-        top: editorPos.bottom - emojiPickerHeight,
+        top: cursorPos.top + offset,
         left: cursorPos.left + offset,
         position: 'fixed',
       };
-    }
-    return {
-      top: cursorPos.top + offset,
-      left: cursorPos.left + offset,
-      position: 'fixed',
-    };
   };
 
   shouldModeTurnOn = (char: string): Position | null | undefined => {
@@ -46,21 +46,21 @@ export default class EmojiPickerHelper {
     if (sc.findPrevious()) {
       return sc.pos.from;
     }
-  };
+  }
 
   shouldOpen = (startPos: Position): boolean => {
     const currentPos = this.editor.getCursor();
     const rangeStr = this.editor.getRange(startPos, currentPos);
 
     return EMOJI_PATTERN.test(rangeStr);
-  };
+  }
 
   getInitialSearchingText = (startPos: Position): void => {
     const currentPos = this.editor.getCursor();
     const rangeStr = this.editor.getRange(startPos, currentPos);
 
     return rangeStr.slice(1); // return without the heading ':'
-  };
+  }
 
   addEmoji = (emoji: { colons: string }, startPosToReplace: Position|null): void => {
     const currentPos = this.editor.getCursor();

+ 1 - 0
packages/app/src/components/PageList/PageListItemL.tsx

@@ -164,6 +164,7 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
     <li
       key={pageData._id}
       className={`list-group-item d-flex align-items-center px-3 px-md-1 ${styleListGroupItem} ${styleActive}`}
+      data-testid="page-list-item-L"
       onClick={clickHandler}
     >
       <div className="text-break w-100">

+ 1 - 1
packages/app/src/components/PrivateLegacyPages.tsx

@@ -74,7 +74,7 @@ const SearchResultListHead = React.memo((props: SearchResultListHeadProps): JSX.
 
   if (isSuccess) {
     return (
-      <div className="card border-success mt-3">
+      <div className="card border-success mt-3" data-testid="search-result-private-legacy-pages">
         <div className="card-body">
           <h2 className="card-title text-success">{t('private_legacy_pages.nopages_title')}</h2>
           <p className="card-text">

+ 1 - 1
packages/app/src/components/SavePageControls.tsx

@@ -85,7 +85,7 @@ export const SavePageControls = (props: Props): JSX.Element | null => {
       }
 
       <UncontrolledButtonDropdown direction="up">
-        <Button id="caret" color="primary" className="btn-submit" onClick={save}>
+        <Button data-testid="save-page-btn" id="caret" color="primary" className="btn-submit" onClick={save}>
           {labelSubmitButton}
         </Button>
         <DropdownToggle caret color="primary" />

+ 1 - 1
packages/app/src/components/Sidebar/RecentChanges.tsx

@@ -36,7 +36,7 @@ const PageItemLower = memo(({ page }: PageItemProps): JSX.Element => {
         <div className="icon-bubble mr-1 d-inline-block"></div>
         <div className="mr-2 grw-list-counts d-inline-block">{page.commentCount}</div>
       </div>
-      <div className="grw-formatted-distance-date small mt-auto">
+      <div className="grw-formatted-distance-date small mt-auto" data-hide-in-vrt>
         <FormattedDistanceDate id={page._id} date={page.updatedAt} />
       </div>
     </div>

+ 1 - 1
packages/app/src/interfaces/activity.ts

@@ -67,7 +67,7 @@ const ACTION_SHARE_LINK_EXPIRED_PAGE_VIEW = 'SHARE_LINK_EXPIRED_PAGE_VIEW';
 const ACTION_SHARE_LINK_NOT_FOUND = 'SHARE_LINK_NOT_FOUND';
 const ACTION_ATTACHMENT_ADD = 'ATTACHMENT_ADD';
 const ACTION_ATTACHMENT_REMOVE = 'ATTACHMENT_REMOVE';
-const ACTION_ATTACHMENT_DOWNLOAD = 'ACTION_ATTACHMENT_DOWNLOAD';
+const ACTION_ATTACHMENT_DOWNLOAD = 'ATTACHMENT_DOWNLOAD';
 const ACTION_SEARCH_PAGE = 'SEARCH_PAGE';
 const ACTION_SEARCH_PAGE_VIEW = 'SEARCH_PAGE_VIEW';
 const ACTION_ADMIN_APP_SETTINGS_UPDATE = 'ADMIN_APP_SETTING_UPDATE';

+ 1 - 1
packages/app/src/server/routes/index.js

@@ -207,7 +207,7 @@ module.exports = function(crowi, app) {
   app.get('/me/*'                                 , loginRequiredStrictly, injectUserUISettings, next.delegateToNext);
   // external-accounts
   // my in-app-notifications
-  // app.get('/me/all-in-app-notifications'   , loginRequiredStrictly, allInAppNotifications.list);
+  // app.get('/me/all-in-app-notifications'   , loginRequiredStrictly, injectUserUISettings, allInAppNotifications.list);
   // app.get('/me/external-accounts'               , loginRequiredStrictly, injectUserUISettings, me.externalAccounts.list);
   // // my drafts
   // app.get('/me/drafts'                          , loginRequiredStrictly, injectUserUISettings, me.drafts.list);

+ 15 - 1
packages/app/src/server/service/installer.ts

@@ -1,6 +1,7 @@
 import path from 'path';
 
 import { Lang } from '@growi/core';
+import { addSeconds } from 'date-fns';
 import ExtensibleCustomError from 'extensible-custom-error';
 import fs from 'graceful-fs';
 import mongoose from 'mongoose';
@@ -78,7 +79,20 @@ export class InstallerService {
         // TODO typescriptize models/user.js and remove eslint-disable-next-line
         // eslint-disable-next-line @typescript-eslint/no-explicit-any
         const Page = mongoose.model('Page') as any;
-        await Page.updateMany({}, { createdAt: initialPagesCreatedAt, updatedAt: initialPagesCreatedAt });
+
+        // Increment timestamp to avoid difference for order in VRT
+        const pagePaths = ['/Sandbox', '/Sandbox/Bootstrap4', '/Sandbox/Diagrams', '/Sandbox/Math'];
+        const promises = pagePaths.map(async(path: string, idx: number) => {
+          const date = addSeconds(initialPagesCreatedAt, idx);
+          return Page.update(
+            { path },
+            {
+              createdAt: date,
+              updatedAt: date,
+            },
+          );
+        });
+        await Promise.all(promises);
       }
       catch (err) {
         logger.error('Failed to update createdAt', err);

+ 9 - 0
packages/app/test/cypress/integration/20-basic-features/access-to-page.spec.ts

@@ -21,6 +21,11 @@ context('Access to page', () => {
     // hide fab
     cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
 
+    // remove animation for screenshot
+    // remove 'blink' class because ::after element cannot be operated
+    // https://stackoverflow.com/questions/5041494/selecting-and-manipulating-css-pseudo-elements-such-as-before-and-after-usin/21709814#21709814
+    cy.get('#Headers').invoke('removeClass', 'blink');
+
     cy.screenshot(`${ssPrefix}-sandbox-headers`);
   });
 
@@ -28,6 +33,8 @@ context('Access to page', () => {
     cy.visit('/Sandbox/Math');
 
     cy.get('mjx-container').should('be.visible');
+    // eslint-disable-next-line cypress/no-unnecessary-waiting
+    cy.wait(2000); // wait for 2 seconds for MathJax.typesetPromise();
 
     cy.screenshot(`${ssPrefix}-sandbox-math`);
   });
@@ -59,6 +66,8 @@ context('Access to /me page', () => {
 
   it('/me is successfully loaded', () => {
     cy.visit('/me', {  });
+    // eslint-disable-next-line cypress/no-unnecessary-waiting
+    cy.wait(500); // wait loading image
     cy.screenshot(`${ssPrefix}-me`);
   });
 

+ 11 - 4
packages/app/test/cypress/integration/20-basic-features/access-to-pagelist.spec.ts

@@ -12,8 +12,7 @@ context('Access to pagelist', () => {
   it('Page list modal is successfully opened ', () => {
     cy.visit('/');
     cy.getByTestid('pageListButton').click({force: true});
-    cy.getByTestid('page-accessories-modal').parent().should('have.class','show');
-    cy.screenshot(`${ssPrefix}1-open-pagelist-modal`);
+    cy.getByTestid('page-accessories-modal').should('be.visible').screenshot(`${ssPrefix}1-open-pagelist-modal`);
   });
 
   it('Successfully duplicate a page from page list', () => {
@@ -21,8 +20,10 @@ context('Access to pagelist', () => {
     cy.getByTestid('pageListButton').click({force: true});
     cy.getByTestid('page-accessories-modal').parent().should('have.class','show').within(() => {
       cy.getByTestid('open-page-item-control-btn').first().click();
-      cy.screenshot(`${ssPrefix}2-click-on-three-dots-menu`);
-      cy.get('.dropdown-menu').should('have.class', 'show').first().within(() => {
+      cy.getByTestid('page-item-control-menu').should('have.class', 'show').first().within(() => {
+        // eslint-disable-next-line cypress/no-unnecessary-waiting
+        cy.wait(300);
+        cy.screenshot(`${ssPrefix}2-open-page-item-control-menu`);
         cy.getByTestid('open-page-duplicate-modal-btn').click();
       });
     });
@@ -50,6 +51,7 @@ context('Access to pagelist', () => {
     cy.getByTestid('page-accessories-modal').parent().should('have.class','show').within(() => {
       cy.get('button.close').eq(0).click();
     });
+
     cy.screenshot(`${ssPrefix}7-page-list-modal-size-fullscreen`, {capture: 'viewport'});
 
     cy.getByTestid('page-accessories-modal').parent().should('have.class','show').within(() => {
@@ -76,6 +78,8 @@ context('Access to timeline', () => {
     cy.getByTestid('page-accessories-modal').parent().should('have.class','show').within(() => {
       cy.get('.nav-title > li').eq(1).find('a').click();
     });
+    // eslint-disable-next-line cypress/no-unnecessary-waiting
+    cy.wait(500); // wait for loading wiki
     cy.screenshot(`${ssPrefix}1-timeline-list`, {capture: 'viewport'});
   });
 
@@ -86,6 +90,9 @@ context('Access to timeline', () => {
       cy.get('.nav-title > li').eq(1).find('a').click();
       cy.get('button.close').eq(0).click();
     });
+    cy.get('.modal').should('be.visible').scrollTo('top');
+    // eslint-disable-next-line cypress/no-unnecessary-waiting
+    cy.wait(500); // wait for loading wiki
     cy.screenshot(`${ssPrefix}2-timeline-list-fullscreen`, {capture: 'viewport'});
     cy.getByTestid('page-accessories-modal').parent().should('have.class','show').within(() => {
       cy.get('button.close').eq(1).click();

+ 82 - 10
packages/app/test/cypress/integration/20-basic-features/use-tools.spec.ts

@@ -9,6 +9,7 @@ context('Switch Sidebar content', () => {
   });
 
   it('PageTree is successfully shown', () => {
+    cy.collapseSidebar(false);
     cy.visit('/page');
     cy.getByTestid('grw-sidebar-nav-primary-page-tree').click();
     // eslint-disable-next-line cypress/no-unnecessary-waiting
@@ -28,20 +29,75 @@ context('Modal for page operation', () => {
     cy.fixture("user-admin.json").then(user => {
       cy.login(user.username, user.password);
     });
+    cy.collapseSidebar(true);
   });
+  it("PageCreateModal is shown and closed successfully", () => {
+    cy.visit('/');
+    cy.getByTestid('newPageBtn').click();
 
-  it("PageCreateModal is shown successfully", () => {
-    cy.visit('/me');
+    cy.getByTestid('page-create-modal').should('be.visible').within(() => {
+      cy.screenshot(`${ssPrefix}new-page-modal-opened`);
+      cy.get('button.close').click();
 
+    });
+    cy.screenshot(`${ssPrefix}page-create-modal-closed`, {capture: 'viewport'});
+  });
+  it("Successfully Create Today's page", () => {
+    const pageName = "Today's page";
+    cy.visit('/');
     cy.getByTestid('newPageBtn').click();
 
-    cy.getByTestid('page-create-modal').should('be.visible').screenshot(`${ssPrefix}-open`);
+    cy.getByTestid('page-create-modal').should('be.visible').within(() => {
+      cy.get('.page-today-input2').type(pageName);
+      cy.screenshot(`${ssPrefix}today-add-page-name`);
+      cy.getByTestid('btn-create-memo').click();
+    });
+    cy.getByTestid('page-editor').should('be.visible');
+    cy.getByTestid('save-page-btn').click();
+    cy.get('body').should('not.have.class', 'on-edit');
 
-    cy.getByTestid('row-create-page-under-below').find('input.form-control').clear().type('/new-page');
-    cy.getByTestid('btn-create-page-under-below').click();
+    cy.getByTestid('grw-contextual-sub-nav').should('be.visible');
+    cy.screenshot(`${ssPrefix}create-today-page`);
+  });
+  it('Successfully create page under specific path', () => {
+    const pageName = 'child';
 
+    cy.visit('/SandBox');
+    cy.getByTestid('newPageBtn').click();
+
+    cy.getByTestid('page-create-modal').should('be.visible').within(() => {
+      cy.get('.rbt-input-main').type(pageName);
+      cy.screenshot(`${ssPrefix}under-path-add-page-name`);
+      cy.getByTestid('btn-create-page-under-below').click();
+    });
     cy.getByTestid('page-editor').should('be.visible');
-    cy.screenshot(`${ssPrefix}-create-clicked`, {capture: 'viewport'});
+    cy.getByTestid('save-page-btn').click();
+    cy.get('body').should('not.have.class', 'on-edit');
+
+    cy.getByTestid('grw-contextual-sub-nav').should('be.visible');
+    cy.screenshot(`${ssPrefix}create-page-under-specific-page`);
+  });
+
+  it('Trying to create template page under the root page fail', () => {
+    cy.visit('/');
+    cy.getByTestid('newPageBtn').click();
+
+    cy.getByTestid('page-create-modal').should('be.visible').within(() => {
+      cy.get('#template-type').click();
+      cy.get('#template-type').next().find('button:eq(0)').click({force: true});
+      cy.get('#dd-template-type').next().find('button').click({force: true});
+    });
+    cy.get('.toast-error').should('be.visible').invoke('attr', 'style', 'opacity: 1');
+    cy.screenshot(`${ssPrefix}create-template-for-children-error`, {capture: 'viewport'});
+    cy.get('.toast-error').should('be.visible').click();
+
+    cy.getByTestid('page-create-modal').should('be.visible').within(() => {
+      cy.get('#template-type').click();
+      cy.get('#template-type').next().find('button:eq(1)').click({force: true});
+      cy.get('#dd-template-type').next().find('button').click({force: true});
+    });
+    cy.get('.toast-error').should('be.visible').invoke('attr', 'style', 'opacity: 1');
+    cy.screenshot(`${ssPrefix}create-template-for-descendants-error`, {capture: 'viewport'});
   });
 
   it('PageDeleteModal is shown successfully', () => {
@@ -89,6 +145,7 @@ context('Open presentation modal', () => {
     cy.fixture("user-admin.json").then(user => {
       cy.login(user.username, user.password);
     });
+    cy.collapseSidebar(true);
   });
 
   it('PresentationModal for "/" is shown successfully', () => {
@@ -115,6 +172,7 @@ context('Page Accessories Modal', () => {
     cy.fixture("user-admin.json").then(user => {
       cy.login(user.username, user.password);
     });
+    cy.collapseSidebar(true);
   });
 
   it('Page History is shown successfully', () => {
@@ -160,6 +218,7 @@ context('Tag Oprations', () =>{
     cy.fixture("user-admin.json").then(user => {
       cy.login(user.username, user.password);
     });
+    cy.collapseSidebar(true);
   });
 
   it('Successfully add new tag', () => {
@@ -188,8 +247,10 @@ context('Tag Oprations', () =>{
       cy.get('div.modal-footer > button').click();
     });
 
+    cy.get('.toast').should('be.visible').trigger('mouseover');
     cy.get('.grw-taglabels-container > form > a').contains(tag).should('exist');
-
+    /* eslint-disable cypress/no-unnecessary-waiting */
+    cy.wait(150); // wait for toastr to change its color occured by mouseover
     cy.screenshot(`${ssPrefix}4-click-done`, {capture: 'viewport'});
 
   });
@@ -203,9 +264,14 @@ context('Tag Oprations', () =>{
     cy.getByTestid('search-result-base').should('be.visible');
     cy.getByTestid('search-result-list').should('be.visible');
     cy.getByTestid('search-result-content').should('be.visible');
+    cy.get('#wiki').should('be.visible');
+    // force to add 'active' to pass VRT: https://github.com/weseek/growi/pull/6603
+    cy.getByTestid('page-list-item-L').first().invoke('addClass', 'active');
     cy.screenshot(`${ssPrefix}1-click-tag-name`, {capture: 'viewport'});
 
     cy.getByTestid('open-page-item-control-btn').first().click({force: true});
+    // eslint-disable-next-line cypress/no-unnecessary-waiting
+    cy.wait(1500); // for wait rendering pagelist info
     cy.screenshot(`${ssPrefix}2-click-three-dots-menu`, {capture: 'viewport'});
 
     cy.getByTestid('open-page-duplicate-modal-btn').first().click({force: true});
@@ -233,24 +299,29 @@ context('Tag Oprations', () =>{
     cy.getByTestid('search-result-base').should('be.visible');
     cy.getByTestid('search-result-list').should('be.visible');
     cy.getByTestid('search-result-content').should('be.visible');
+    cy.get('#wiki').should('be.visible');
     cy.screenshot(`${ssPrefix}1-click-tag-name`, {capture: 'viewport'});
 
     cy.getByTestid('search-result-list').within(() => {
       cy.get('.list-group-item').each(($row) => {
         if($row.find('a').text() === oldPageName){
           cy.wrap($row).within(() => {
-            cy.getByTestid('open-page-item-control-btn').click();
+            cy.getByTestid('open-page-item-control-btn').first().click();
+            cy.getByTestid('page-item-control-menu').should('have.class', 'show').first().within(() => {
+            // eslint-disable-next-line cypress/no-unnecessary-waiting
+            cy.wait(300);
+            cy.screenshot(`${ssPrefix}2-open-page-item-control-menu`);
+            })
           });
         }
       });
     });
-    cy.screenshot(`${ssPrefix}2-click-three-dots-menu`, {capture: 'viewport'});
 
     cy.getByTestid('search-result-list').within(() => {
       cy.get('.list-group-item').each(($row) => {
         if($row.find('a').text() === oldPageName){
           cy.wrap($row).within(() => {
-            cy.getByTestid('open-page-move-rename-modal-btn').click();
+            cy.getByTestid('open-page-move-rename-modal-btn').click({force: true});
           });
         }
       });
@@ -265,6 +336,7 @@ context('Tag Oprations', () =>{
     });
 
     cy.visit(`/${newPageName}`);
+    cy.getByTestid('grw-tag-labels').should('be.visible');
     cy.screenshot(`${ssPrefix}4-new-page-name-applied`, {capture: 'viewport'});
   });
 

+ 14 - 10
packages/app/test/cypress/integration/21-basic-features-for-guest/access-to-page.spec.ts

@@ -1,18 +1,18 @@
 context('Access to page by guest', () => {
   const ssPrefix = 'access-to-page-by-guest-';
 
-  beforeEach(() => {
-    // collapse sidebar
-    cy.collapseSidebar(true);
-  });
-
   it('/Sandbox is successfully loaded', () => {
     cy.visit('/Sandbox', {  });
+    cy.collapseSidebar(true, true);
     cy.screenshot(`${ssPrefix}-sandbox`);
   });
 
   it('/Sandbox with anchor hash is successfully loaded', () => {
     cy.visit('/Sandbox#Headers');
+    cy.collapseSidebar(true, true);
+
+    // eslint-disable-next-line cypress/no-unnecessary-waiting
+    cy.wait(500);
 
     // hide fab
     cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
@@ -22,14 +22,22 @@ context('Access to page by guest', () => {
 
   it('/Sandbox/Math is successfully loaded', () => {
     cy.visit('/Sandbox/Math');
+    cy.collapseSidebar(true, true);
 
     cy.get('mjx-container').should('be.visible');
+    // eslint-disable-next-line cypress/no-unnecessary-waiting
+    cy.wait(2000); // wait for 2 seconds for MathJax.typesetPromise();
 
     cy.screenshot(`${ssPrefix}-sandbox-math`);
   });
 
   it('/Sandbox with edit is successfully loaded', () => {
     cy.visit('/Sandbox#edit');
+    cy.collapseSidebar(true, true);
+
+    // eslint-disable-next-line cypress/no-unnecessary-waiting
+    cy.wait(1000);
+
     cy.screenshot(`${ssPrefix}-sandbox-edit-page`);
   })
 
@@ -55,13 +63,9 @@ context('Access to /me page', () => {
 context('Access to special pages by guest', () => {
   const ssPrefix = 'access-to-special-pages-by-guest-';
 
-  beforeEach(() => {
-    // collapse sidebar
-    cy.collapseSidebar(true);
-  });
-
   it('/trash is successfully loaded', () => {
     cy.visit('/trash', {  });
+    cy.collapseSidebar(true, true);
     cy.getByTestid('trash-page-list').should('be.visible');
     cy.screenshot(`${ssPrefix}-trash`);
   });

+ 53 - 12
packages/app/test/cypress/integration/30-search/search.spec.ts

@@ -16,8 +16,10 @@ context('Access to search result page', () => {
     cy.getByTestid('search-result-base').should('be.visible');
     cy.getByTestid('search-result-list').should('be.visible');
     cy.getByTestid('search-result-content').should('be.visible');
-
-    cy.screenshot(`${ssPrefix}-with-q`);
+    cy.get('#wiki').should('be.visible');
+    // for avoid mismatch by auto scrolling
+    cy.get('.search-result-content-body-container').scrollTo('top');
+    cy.screenshot(`${ssPrefix}with-q`);
   });
 
   it('checkboxes behaviors', () => {
@@ -26,27 +28,31 @@ context('Access to search result page', () => {
     cy.getByTestid('search-result-base').should('be.visible');
     cy.getByTestid('search-result-list').should('be.visible');
     cy.getByTestid('search-result-content').should('be.visible');
+    cy.get('#wiki').should('be.visible');
+    // for avoid mismatch by auto scrolling
+    cy.get('.search-result-content-body-container').scrollTo('top');
+
+    // force to add 'active' to pass VRT: https://github.com/weseek/growi/pull/6603
+    cy.getByTestid('page-list-item-L').first().invoke('addClass', 'active');
 
     cy.getByTestid('cb-select').first().click({force: true});
-    cy.screenshot(`${ssPrefix}-the-first-checkbox-on`);
+    cy.screenshot(`${ssPrefix}the-first-checkbox-on`);
     cy.getByTestid('cb-select').first().click({force: true});
-    cy.screenshot(`${ssPrefix}-the-first-checkbox-off`);
+    cy.screenshot(`${ssPrefix}the-first-checkbox-off`);
 
     // click select all checkbox
     cy.getByTestid('cb-select-all').click({force: true});
-    cy.screenshot(`${ssPrefix}-the-select-all-checkbox-1`);
+    cy.screenshot(`${ssPrefix}the-select-all-checkbox-1`);
     cy.getByTestid('cb-select').first().click({force: true});
-    cy.screenshot(`${ssPrefix}-the-select-all-checkbox-2`);
+    cy.screenshot(`${ssPrefix}the-select-all-checkbox-2`);
     cy.getByTestid('cb-select').first().click({force: true});
-    cy.screenshot(`${ssPrefix}-the-select-all-checkbox-3`);
+    cy.screenshot(`${ssPrefix}the-select-all-checkbox-3`);
     cy.getByTestid('cb-select-all').click({force: true});
-    cy.screenshot(`${ssPrefix}-the-select-all-checkbox-4`);
+    cy.screenshot(`${ssPrefix}the-select-all-checkbox-4`);
   });
 
 });
 
-
-
 context('Access to legacy private pages', () => {
   const ssPrefix = 'access-to-legacy-private-pages-directly-';
 
@@ -63,8 +69,9 @@ context('Access to legacy private pages', () => {
     cy.visit('/_private-legacy-pages');
 
     cy.getByTestid('search-result-base').should('be.visible');
+    cy.getByTestid('search-result-private-legacy-pages').should('be.visible');
 
-    cy.screenshot(`${ssPrefix}-shown`);
+    cy.screenshot(`${ssPrefix}shown`);
   });
 
 });
@@ -98,9 +105,20 @@ context('Search all pages', () => {
     cy.getByTestid('search-result-base').should('be.visible');
     cy.getByTestid('search-result-list').should('be.visible');
     cy.getByTestid('search-result-content').should('be.visible');
+    cy.get('#wiki').should('be.visible');
+    // force to add 'active' to pass VRT: https://github.com/weseek/growi/pull/6603
+    cy.getByTestid('page-list-item-L').first().invoke('addClass', 'active');
+    // for avoid mismatch by auto scrolling
+    cy.get('.search-result-content-body-container').scrollTo('top');
+    // eslint-disable-next-line cypress/no-unnecessary-waiting
+    cy.wait(1500);
     cy.screenshot(`${ssPrefix}3-search-page-results`, { capture: 'viewport'});
 
     cy.getByTestid('open-page-item-control-btn').eq(1).click();
+    cy.getByTestid('search-result-content').should('be.visible');
+    cy.get('#wiki').should('be.visible');
+    // for avoid mismatch by auto scrolling
+    cy.get('.search-result-content-body-container').scrollTo('top');
     cy.screenshot(`${ssPrefix}4-click-three-dots-menu`, {capture: 'viewport'});
 
     //Add bookmark
@@ -158,12 +176,18 @@ context('Search all pages', () => {
     cy.getByTestid('search-result-base').should('be.visible');
     cy.getByTestid('search-result-list').should('be.visible');
     cy.getByTestid('search-result-content').should('be.visible');
-
+    cy.get('#wiki').should('be.visible');
+    // force to add 'active' to pass VRT: https://github.com/weseek/growi/pull/6603
+    cy.getByTestid('page-list-item-L').first().invoke('addClass', 'active');
     cy.screenshot(`${ssPrefix}2-search-with-tag-result`, {capture: 'viewport'});
+
     cy.getByTestid('open-page-item-control-btn').first().click();
+    cy.getByTestid('search-result-content').should('be.visible');
+    cy.get('#wiki').should('be.visible');
     cy.screenshot(`${ssPrefix}3-click-three-dots-menu-search-with-tag`, {capture: 'viewport'});
 
   });
+
   it('Successfully order page search results by tag', () => {
     const tag = 'help';
 
@@ -172,6 +196,9 @@ context('Search all pages', () => {
     cy.getByTestid('search-result-base').should('be.visible');
     cy.getByTestid('search-result-list').should('be.visible');
     cy.getByTestid('search-result-content').should('be.visible');
+    cy.get('#wiki').should('be.visible');
+    // force to add 'active' to pass VRT: https://github.com/weseek/growi/pull/6603
+    cy.getByTestid('page-list-item-L').first().invoke('addClass', 'active');
     cy.screenshot(`${ssPrefix}1-tag-order-click-tag-name`, {capture: 'viewport'});
 
     cy.get('.grw-search-page-nav').within(() => {
@@ -182,6 +209,7 @@ context('Search all pages', () => {
     cy.getByTestid('search-result-base').should('be.visible');
     cy.getByTestid('search-result-list').should('be.visible');
     cy.getByTestid('search-result-content').should('be.visible');
+    cy.get('#wiki').should('be.visible');
     cy.screenshot(`${ssPrefix}2-tag-order-by-relevance`);
 
     cy.get('.grw-search-page-nav').within(() => {
@@ -192,6 +220,7 @@ context('Search all pages', () => {
     cy.getByTestid('search-result-base').should('be.visible');
     cy.getByTestid('search-result-list').should('be.visible');
     cy.getByTestid('search-result-content').should('be.visible');
+    cy.get('#wiki').should('be.visible');
     cy.screenshot(`${ssPrefix}3-tag-order-by-creation-date`);
 
     cy.get('.grw-search-page-nav').within(() => {
@@ -202,6 +231,7 @@ context('Search all pages', () => {
     cy.getByTestid('search-result-base').should('be.visible');
     cy.getByTestid('search-result-list').should('be.visible');
     cy.getByTestid('search-result-content').should('be.visible');
+    cy.get('#wiki').should('be.visible');
     cy.screenshot(`${ssPrefix}4-tag-order-by-last-update-date`);
   });
 
@@ -235,9 +265,20 @@ context('Search current tree with "prefix":', () => {
     cy.getByTestid('search-result-base').should('be.visible');
     cy.getByTestid('search-result-list').should('be.visible');
     cy.getByTestid('search-result-content').should('be.visible');
+    cy.get('#wiki').should('be.visible');
+    // force to add 'active' to pass VRT: https://github.com/weseek/growi/pull/6603
+    cy.getByTestid('page-list-item-L').first().invoke('addClass', 'active');
+    // for avoid mismatch by auto scrolling
+    cy.get('.search-result-content-body-container').scrollTo('top');
+    // eslint-disable-next-line cypress/no-unnecessary-waiting
+    cy.wait(1500);
     cy.screenshot(`${ssPrefix}3-search-page-results`, { capture: 'viewport'});
 
     cy.getByTestid('open-page-item-control-btn').first().click();
+    cy.getByTestid('search-result-content').should('be.visible');
+    cy.get('#wiki').should('be.visible');
+    // for avoid mismatch by auto scrolling
+    cy.get('.search-result-content-body-container').scrollTo('top');
     cy.screenshot(`${ssPrefix}4-click-three-dots-menu`, {capture: 'viewport'});
   });
 

+ 2 - 0
packages/app/test/cypress/integration/40-admin/access-to-admin-page.spec.ts

@@ -51,6 +51,8 @@ context('Access to Admin page', () => {
   it('/admin/customize is successfully loaded', () => {
     cy.visit('/admin/customize');
     cy.getByTestid('admin-customize').should('be.visible');
+    /* eslint-disable cypress/no-unnecessary-waiting */
+    cy.wait(500); // wait for loading layout image
     cy.screenshot(`${ssPrefix}-admin-customize`);
   });
 

+ 21 - 3
packages/app/test/cypress/integration/50-sidebar/access-to-side-bar.spec.ts

@@ -27,13 +27,20 @@ context('Access to sidebar', () => {
     });
 
     cy.getByTestid('grw-recent-changes').should('be.visible');
+    cy.get('.list-group-item').should('be.visible');
 
-    cy.getByTestid('grw-contextual-navigation-sub').screenshot(`${ssPrefix}recent-changes-1-page-list`);
+    // Avoid blackout misalignment
+    cy.scrollTo('center');
+    cy.screenshot(`${ssPrefix}recent-changes-1-page-list`);
 
     cy.get('#grw-sidebar-contents-wrapper').within(() => {
       cy.get('#recentChangesResize').click({force: true});
-      cy.screenshot(`${ssPrefix}recent-changes-2-switch-sidebar-size`);
+      cy.get('.list-group-item').should('be.visible');
     });
+
+    // Avoid blackout misalignment
+    cy.scrollTo('center');
+    cy.screenshot(`${ssPrefix}recent-changes-2-switch-sidebar-size`);
   });
 
   it('Successfully create a custom sidebar page', () => {
@@ -52,8 +59,17 @@ context('Access to sidebar', () => {
     cy.get('.grw-sidebar-content-header.h5').find('a').click();
     cy.get('.CodeMirror textarea').type(content, {force: true});
     cy.screenshot(`${ssPrefix}custom-sidebar-2-custom-sidebar-editor`);
-    cy.get('.dropup > .btn-submit').click();
+    cy.getByTestid('save-page-btn').click();
     cy.get('body').should('not.have.class', 'on-edit');
+
+    // What to do when UserUISettings is not saved in time
+    cy.getByTestid('grw-sidebar-nav-primary-custom-sidebar').then(($el) => {
+      if (!$el.hasClass('active')) {
+        cy.wrap($el).click();
+      }
+    });
+
+    cy.get('.grw-custom-sidebar-content').should('be.visible');
     cy.getByTestid('grw-contextual-navigation-sub').screenshot(`${ssPrefix}custom-sidebar-3-custom-sidebar-created`);
   });
 
@@ -142,6 +158,7 @@ context('Access to sidebar', () => {
 
   it('Successfully access to My Drafts page', () => {
     cy.visit('/');
+    cy.collapseSidebar(true);
     cy.get('.grw-sidebar-nav-secondary-container').within(() => {
       cy.get('a[href*="/me/drafts"]').click();
     });
@@ -159,6 +176,7 @@ context('Access to sidebar', () => {
 
   it('Successfully access to trash page', () => {
     cy.visit('/');
+    cy.collapseSidebar(true);
     cy.get('.grw-sidebar-nav-secondary-container').within(() => {
       cy.get('a[href*="/trash"]').click();
     });

+ 8 - 3
packages/app/test/cypress/integration/50-sidebar/switching-sidebar-mode.spec.ts

@@ -21,13 +21,18 @@ context('Switch sidebar mode', () => {
 
   it('Switching sidebar mode', () => {
     cy.visit('/');
-    cy.get('.grw-apperance-mode-dropdown').click();
+    cy.collapseSidebar(true, true)
+    cy.get('.grw-apperance-mode-dropdown').first().click();
 
     cy.get('[for="swSidebarMode"]').click({force: true});
-    cy.screenshot(`${ssPrefix}-switch-sidebar-mode`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-switch-sidebar-mode`, {
+      blackout: ['#revision-toc', '[data-hide-in-vrt=true]'],
+    })
 
     cy.get('[for="swSidebarMode"]').click({force: true});
-    cy.screenshot(`${ssPrefix}-switch-sidebar-mode-back`, { capture: 'viewport' });
+    cy.screenshot(`${ssPrefix}-switch-sidebar-mode-back`, {
+      blackout: ['#revision-toc', '[data-hide-in-vrt=true]'],
+    })
   });
 
 });

+ 2 - 2
packages/app/test/cypress/support/commands.ts

@@ -40,9 +40,9 @@ Cypress.Commands.add('login', (username, password) => {
 
 let isSidebarCollapsed: boolean | undefined;
 
-Cypress.Commands.add('collapseSidebar', (isCollapsed) => {
+Cypress.Commands.add('collapseSidebar', (isCollapsed, force=false) => {
 
-  if (isSidebarCollapsed === isCollapsed) {
+  if (!force && isSidebarCollapsed === isCollapsed) {
     return;
   }
 

+ 1 - 1
packages/app/test/cypress/support/index.ts

@@ -36,7 +36,7 @@ declare global {
     interface Chainable {
        getByTestid(selector: string, options?: Partial<Loggable & Timeoutable & Withinable & Shadow>): Chainable<JQuery<Element>>,
        login(username: string, password: string): Chainable<void>,
-       collapseSidebar(isCollapsed: boolean): Chainable<void>,
+       collapseSidebar(isCollapsed: boolean, force?: boolean): Chainable<void>,
     }
   }
 }