[ARVADOS-WORKBENCH2] updated: 1.4.1-339-gfc898afb

Git user git at public.arvados.org
Mon May 18 13:16:28 UTC 2020


Summary of changes:
 cypress/integration/collection-panel.spec.js       | 117 +++++++++++++++++++++
 cypress/support/commands.js                        |  22 ++++
 src/common/config.ts                               |  39 ++++++-
 src/common/webdav.ts                               |   2 +-
 .../collection-panel-files.tsx                     |  11 +-
 src/components/context-menu/context-menu.tsx       |   2 +-
 src/components/file-tree/file-tree-item.tsx        |   1 +
 src/components/icon/icon.tsx                       |   4 +-
 src/index.tsx                                      |  16 ++-
 .../property-key-field.tsx                         |   2 +
 .../property-value-field.tsx                       |   2 +
 .../resource-properties-form.tsx                   |   2 +-
 src/views/collection-panel/collection-panel.tsx    |  28 +++--
 src/views/login-panel/login-panel.tsx              |  21 ++--
 tools/arvados_config.yml                           |   3 +-
 15 files changed, 232 insertions(+), 40 deletions(-)
 create mode 100644 cypress/integration/collection-panel.spec.js

       via  fc898afbfa340c57062887083c96bc197fc283d0 (commit)
       via  4c6f753bed0deee685b469be0b399bd61d457de8 (commit)
       via  ca7a29e2ac03703afeff3248d1b909f42b89ab19 (commit)
       via  366e40d9ec1e9068d80a5c3a42c7d0ad31de4a08 (commit)
       via  6d2e6d292161d566f54e94f048805569ede8e3d5 (commit)
       via  bc49fa261517a5027980570a903b6b6aa5c1f82a (commit)
       via  ee9dac4509683f2fbe5fda146bd9c18f4b765337 (commit)
      from  d8a3b5fdd6f606800e9b321acb3fca10c5183cb9 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


commit fc898afbfa340c57062887083c96bc197fc283d0
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Mon May 18 10:12:23 2020 -0300

    16118: Adds test checking writable/readonly collection UI changes.
    
    WIP: There's deactivated code that shows a snackbar whenever a service request
    returns an error, no matter if the error is handled somewhere up in the stack.
    I think that isn't a good approach, also it prevents the 'readonly' case to
    work because the snackbar appears in fron of a menu button and cannot be
    clicked.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/cypress/integration/collection-panel.spec.js b/cypress/integration/collection-panel.spec.js
index 287bfe10..0ae72809 100644
--- a/cypress/integration/collection-panel.spec.js
+++ b/cypress/integration/collection-panel.spec.js
@@ -28,21 +28,90 @@ describe('Collection panel tests', function() {
         cy.clearLocalStorage()
     })
 
-    it('shows a collection by URL', function() {
+    it('shows collection by URL', function() {
         cy.loginAs(activeUser);
-        cy.createCollection(adminUser.token, {
-            name: 'Test collection',
-            owner_uuid: activeUser.user.uuid,
-            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"})
-        .as('testCollection').then(function() {
-            cy.visit(`/collections/${this.testCollection.uuid}`);
-            cy.get('[data-cy=collection-info-panel]')
-                .should('contain', this.testCollection.name)
-                .and('contain', this.testCollection.uuid);
-            cy.get('[data-cy=collection-files-panel]')
-                .should('contain', 'bar');
+        [true, false].map(function(isWritable) {
+            // Creates the collection using the admin token so we can set up
+            // a bogus manifest text without block signatures.
+            cy.createCollection(adminUser.token, {
+                name: 'Test collection',
+                owner_uuid: isWritable
+                    ? activeUser.user.uuid
+                    : adminUser.user.uuid,
+                properties: {someKey: 'someValue'},
+                manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"})
+            .as('testCollection').then(function() {
+                if (isWritable === false) {
+                    // Share collection as read-only
+                    cy.do_request('POST', '/arvados/v1/links', {
+                        link: JSON.stringify({
+                            name: 'can_read',
+                            link_class: 'permission',
+                            head_uuid: this.testCollection.uuid,
+                            tail_uuid: activeUser.user.uuid
+                        })
+                    }, null, adminUser.token, null);
+                }
+                cy.visit(`/collections/${this.testCollection.uuid}`);
+                // Check that name & uuid are correct.
+                cy.get('[data-cy=collection-info-panel]')
+                    .should('contain', this.testCollection.name)
+                    .and(`${isWritable ? 'not.': ''}contain`, 'Read-only')
+                    .and('contain', this.testCollection.uuid);
+                // Check that both read and write operations are available on
+                // the 'More options' menu.
+                cy.get('[data-cy=collection-panel-options-btn]')
+                    .click()
+                cy.get('[data-cy=context-menu]')
+                    .should('contain', 'Add to favorites')
+                    .and(`${isWritable ? '' : 'not.'}contain`, 'Edit collection')
+                    .type('{esc}'); // Collapse the options menu
+                cy.get('[data-cy=collection-properties-panel]')
+                    .should('contain', 'someKey')
+                    .and('contain', 'someValue')
+                    .and('not.contain', 'anotherKey')
+                    .and('not.contain', 'anotherValue')
+                // Check that properties can be added.
+                if (isWritable === true) {
+                    cy.get('[data-cy=collection-properties-form]').within(() => {
+                        cy.get('[data-cy=property-field-key]').within(() => {
+                            cy.get('input').type('anotherKey');
+                        });
+                        cy.get('[data-cy=property-field-value]').within(() => {
+                            cy.get('input').type('anotherValue');
+                        });
+                        cy.root().submit();
+                    })
+                    cy.get('[data-cy=collection-properties-panel]')
+                        .should('contain', 'anotherKey')
+                        .and('contain', 'anotherValue')
+                }
+                // Check that the file listing show both read & write operations
+                cy.get('[data-cy=collection-files-panel]').within(() => {
+                    cy.root().should('contain', 'bar');
+                    cy.get('[data-cy=upload-button]')
+                        .should(`${isWritable ? '' : 'not.'}contain`, 'Upload data');
+                });
+                // Hamburger 'more options' menu button
+                cy.get('[data-cy=collection-files-panel-options-btn]')
+                    .click()
+                cy.get('[data-cy=context-menu]')
+                    .should('contain', 'Select all')
+                    .click()
+                cy.get('[data-cy=collection-files-panel-options-btn]')
+                    .click()
+                cy.get('[data-cy=context-menu]')
+                    .should('contain', 'Download selected')
+                    .and(`${isWritable ? '' : 'not.'}contain`, 'Remove selected')
+                    .type('{esc}'); // Collapse the options menu
+                // File item 'more options' button
+                cy.get('[data-cy=file-item-options-btn')
+                    .click()
+                cy.get('[data-cy=context-menu]')
+                    .should('contain', 'Download')
+                    .and(`${isWritable ? '' : 'not.'}contain`, 'Remove')
+                    .type('{esc}'); // Collapse
+            })
         })
     })
-
-    // it('')
 })
\ No newline at end of file
diff --git a/src/components/collection-panel-files/collection-panel-files.tsx b/src/components/collection-panel-files/collection-panel-files.tsx
index 3de4068f..48b36be1 100644
--- a/src/components/collection-panel-files/collection-panel-files.tsx
+++ b/src/components/collection-panel-files/collection-panel-files.tsx
@@ -50,12 +50,15 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
 export const CollectionPanelFiles =
     withStyles(styles)(
         ({ onItemMenuOpen, onOptionsMenuOpen, onUploadDataClick, classes, isWritable, ...treeProps }: CollectionPanelFilesProps & WithStyles<CssRules>) =>
-            <Card className={classes.root}>
+            <Card data-cy='collection-files-panel' className={classes.root}>
                 <CardHeader
                     title="Files"
                     classes={{ action: classes.button }}
                     action={
-                        isWritable && <Button onClick={onUploadDataClick}
+                        isWritable &&
+                        <Button
+                            data-cy='upload-button'
+                            onClick={onUploadDataClick}
                             variant='contained'
                             color='primary'
                             size='small'>
@@ -67,7 +70,9 @@ export const CollectionPanelFiles =
                     className={classes.cardSubheader}
                     action={
                         <Tooltip title="More options" disableFocusListener>
-                            <IconButton onClick={(ev) => onOptionsMenuOpen(ev, isWritable)}>
+                            <IconButton
+                                data-cy='collection-files-panel-options-btn'
+                                onClick={(ev) => onOptionsMenuOpen(ev, isWritable)}>
                                 <CustomizeTableIcon />
                             </IconButton>
                         </Tooltip>
diff --git a/src/components/context-menu/context-menu.tsx b/src/components/context-menu/context-menu.tsx
index 98456dad..ecade812 100644
--- a/src/components/context-menu/context-menu.tsx
+++ b/src/components/context-menu/context-menu.tsx
@@ -32,7 +32,7 @@ export class ContextMenu extends React.PureComponent<ContextMenuProps> {
             transformOrigin={DefaultTransformOrigin}
             anchorOrigin={DefaultTransformOrigin}
             onContextMenu={this.handleContextMenu}>
-            <List dense>
+            <List data-cy='context-menu' dense>
                 {items.map((group, groupIndex) =>
                     <React.Fragment key={groupIndex}>
                         {group.map((item, actionIndex) =>
diff --git a/src/components/file-tree/file-tree-item.tsx b/src/components/file-tree/file-tree-item.tsx
index dc8f09b9..23273dac 100644
--- a/src/components/file-tree/file-tree-item.tsx
+++ b/src/components/file-tree/file-tree-item.tsx
@@ -53,6 +53,7 @@ export const FileTreeItem = withStyles(fileTreeItemStyle)(
                         variant="caption">{formatFileSize(item.data.size)}</Typography>
                     <Tooltip title="More options" disableFocusListener>
                         <IconButton
+                            data-cy='file-item-options-btn'
                             className={classes.button}
                             onClick={this.handleClick}>
                             <MoreOptionsIcon className={classes.moreOptions} />
diff --git a/src/index.tsx b/src/index.tsx
index 16759b7f..eec60dc1 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -104,18 +104,14 @@ fetchConfig()
             },
             errorFn: (id, error) => {
                 console.error("Backend error:", error);
-                if (error.errors) {
+                if (false) { // WIP: Should we mix backend with UI code?
                     store.dispatch(snackbarActions.OPEN_SNACKBAR({
-                        message: `${error.errors[0]}`,
+                        message: `${error.errors
+                            ? error.errors[0]
+                            : error.message}`,
                         kind: SnackbarKind.ERROR,
-                        hideDuration: 8000
-                    }));
-                } else {
-                    store.dispatch(snackbarActions.OPEN_SNACKBAR({
-                        message: `${error.message}`,
-                        kind: SnackbarKind.ERROR,
-                        hideDuration: 8000
-                    }));
+                        hideDuration: 8000})
+                    );
                 }
             }
         });
diff --git a/src/views-components/resource-properties-form/property-key-field.tsx b/src/views-components/resource-properties-form/property-key-field.tsx
index 1f921188..d17f50d4 100644
--- a/src/views-components/resource-properties-form/property-key-field.tsx
+++ b/src/views-components/resource-properties-form/property-key-field.tsx
@@ -16,11 +16,13 @@ export const PROPERTY_KEY_FIELD_ID = 'keyID';
 
 export const PropertyKeyField = connectVocabulary(
     ({ vocabulary, skipValidation }: VocabularyProp & ValidationProp) =>
+        <span data-cy='property-field-key'>
         <Field
             name={PROPERTY_KEY_FIELD_NAME}
             component={PropertyKeyInput}
             vocabulary={vocabulary}
             validate={skipValidation ? undefined : getValidation(vocabulary)} />
+        </span>
 );
 
 const PropertyKeyInput = ({ vocabulary, ...props }: WrappedFieldProps & VocabularyProp) =>
diff --git a/src/views-components/resource-properties-form/property-value-field.tsx b/src/views-components/resource-properties-form/property-value-field.tsx
index 99745199..c5a5071f 100644
--- a/src/views-components/resource-properties-form/property-value-field.tsx
+++ b/src/views-components/resource-properties-form/property-value-field.tsx
@@ -28,11 +28,13 @@ const connectVocabularyAndPropertyKey = compose(
 
 export const PropertyValueField = connectVocabularyAndPropertyKey(
     ({ skipValidation, ...props }: PropertyValueFieldProps) =>
+        <span data-cy='property-field-value'>
         <Field
             name={PROPERTY_VALUE_FIELD_NAME}
             component={PropertyValueInput}
             validate={skipValidation ? undefined : getValidation(props)}
             {...props} />
+        </span>
 );
 
 const PropertyValueInput = ({ vocabulary, propertyKey, ...props }: WrappedFieldProps & PropertyValueFieldProps) =>
diff --git a/src/views-components/resource-properties-form/resource-properties-form.tsx b/src/views-components/resource-properties-form/resource-properties-form.tsx
index db40e4a7..0632b97c 100644
--- a/src/views-components/resource-properties-form/resource-properties-form.tsx
+++ b/src/views-components/resource-properties-form/resource-properties-form.tsx
@@ -20,7 +20,7 @@ export interface ResourcePropertiesFormData {
 export type ResourcePropertiesFormProps = InjectedFormProps<ResourcePropertiesFormData> & WithStyles<GridClassKey>;
 
 export const ResourcePropertiesForm = ({ handleSubmit, submitting, invalid, classes }: ResourcePropertiesFormProps ) =>
-    <form onSubmit={handleSubmit}>
+    <form data-cy='collection-properties-form' onSubmit={handleSubmit}>
         <Grid container spacing={16} classes={classes}>
             <Grid item xs>
                 <PropertyKeyField />
diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx
index be2afc72..defe73b9 100644
--- a/src/views/collection-panel/collection-panel.tsx
+++ b/src/views/collection-panel/collection-panel.tsx
@@ -29,7 +29,7 @@ import { GroupResource } from '~/models/group';
 import { UserResource } from '~/models/user';
 import { getUserUuid } from '~/common/getuser';
 
-type CssRules = 'card' | 'iconHeader' | 'tag' | 'label' | 'value' | 'link' | 'centeredLabel';
+type CssRules = 'card' | 'iconHeader' | 'tag' | 'label' | 'value' | 'link' | 'centeredLabel' | 'readOnlyChip';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     card: {
@@ -60,6 +60,9 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         '&:hover': {
             cursor: 'pointer'
         }
+    },
+    readOnlyChip: {
+        marginLeft: theme.spacing.unit
     }
 });
 
@@ -101,6 +104,7 @@ export const CollectionPanel = withStyles(styles)(
                                 action={
                                     <Tooltip title="More options" disableFocusListener>
                                         <IconButton
+                                            data-cy='collection-panel-options-btn'
                                             aria-label="More options"
                                             onClick={this.handleContextMenu}>
                                             <MoreOptionsIcon />
@@ -111,7 +115,7 @@ export const CollectionPanel = withStyles(styles)(
                                     <span>
                                         <IllegalNamingWarning name={item.name}/>
                                         {item.name}
-                                        {isWritable || <Chip variant="outlined" icon={<ReadOnlyIcon />} label="Read-only"/>}
+                                        {isWritable || <Chip variant="outlined" icon={<ReadOnlyIcon />} label="Read-only" className={classes.readOnlyChip} />}
                                     </span>
                                 }
                                 titleTypographyProps={this.titleProps}
@@ -142,7 +146,7 @@ export const CollectionPanel = withStyles(styles)(
                             </CardContent>
                         </Card>
 
-                        <Card className={classes.card}>
+                        <Card data-cy='collection-properties-panel' className={classes.card}>
                             <CardHeader title="Properties" />
                             <CardContent>
                                 <Grid container direction="column">
@@ -173,7 +177,7 @@ export const CollectionPanel = withStyles(styles)(
                                 </Grid>
                             </CardContent>
                         </Card>
-                        <div className={classes.card} data-cy="collection-files-panel">
+                        <div className={classes.card}>
                             <CollectionPanelFiles isWritable={isWritable} />
                         </div>
                     </>

commit 4c6f753bed0deee685b469be0b399bd61d457de8
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Fri May 15 11:21:18 2020 -0300

    16118: Fixes WebDAV request URL.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/common/webdav.ts b/src/common/webdav.ts
index a09e8fdd..3f55a841 100644
--- a/src/common/webdav.ts
+++ b/src/common/webdav.ts
@@ -61,7 +61,7 @@ export class WebDAV {
     private request = (config: RequestConfig) => {
         return new Promise<XMLHttpRequest>((resolve, reject) => {
             const r = this.createRequest();
-            r.open(config.method, this.defaults.baseURL + config.url);
+            r.open(config.method, `${this.defaults.baseURL}/${config.url}`);
             const headers = { ...this.defaults.headers, ...config.headers };
             Object
                 .keys(headers)

commit ca7a29e2ac03703afeff3248d1b909f42b89ab19
Merge: bc49fa26 366e40d9
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Thu May 14 20:46:57 2020 -0300

    16118: Merge branch '15881-ldap' into 16118-readonly-collections-lucas
    
    Need this before it gets merged to master to successfully run e2e test.


commit bc49fa261517a5027980570a903b6b6aa5c1f82a
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Mon May 11 16:53:27 2020 -0300

    16118: Changes read-only padlock icon with an explicit legend.
    
    Also adds cypress-specific attributes to be able to get UI elements in a more
    readable way.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/components/icon/icon.tsx b/src/components/icon/icon.tsx
index 163010e4..a5fa5ddd 100644
--- a/src/components/icon/icon.tsx
+++ b/src/components/icon/icon.tsx
@@ -53,7 +53,7 @@ import SettingsEthernet from '@material-ui/icons/SettingsEthernet';
 import Star from '@material-ui/icons/Star';
 import StarBorder from '@material-ui/icons/StarBorder';
 import Warning from '@material-ui/icons/Warning';
-import Visibility from '@material-ui/icons/Lock';
+import ErrorOutline from '@material-ui/icons/ErrorOutline';
 import VpnKey from '@material-ui/icons/VpnKey';
 
 export type IconType = React.SFC<{ className?: string, style?: object }>;
@@ -97,7 +97,7 @@ export const ProcessIcon: IconType = (props) => <BubbleChart {...props} />;
 export const ProjectIcon: IconType = (props) => <Folder {...props} />;
 export const ProjectsIcon: IconType = (props) => <Inbox {...props} />;
 export const ProvenanceGraphIcon: IconType = (props) => <DeviceHub {...props} />;
-export const ReadOnlyIcon: IconType = (props) => <Visibility {...props} />;
+export const ReadOnlyIcon: IconType = (props) => <ErrorOutline {...props} />;
 export const RemoveIcon: IconType = (props) => <Delete {...props} />;
 export const RemoveFavoriteIcon: IconType = (props) => <Star {...props} />;
 export const PublicFavoriteIcon: IconType = (props) => <Public {...props} />;
diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx
index 64de885f..be2afc72 100644
--- a/src/views/collection-panel/collection-panel.tsx
+++ b/src/views/collection-panel/collection-panel.tsx
@@ -5,7 +5,7 @@
 import * as React from 'react';
 import {
     StyleRulesCallback, WithStyles, withStyles, Card,
-    CardHeader, IconButton, CardContent, Grid, Tooltip
+    CardHeader, IconButton, CardContent, Grid, Tooltip, Chip
 } from '@material-ui/core';
 import { connect, DispatchProp } from "react-redux";
 import { RouteComponentProps } from 'react-router';
@@ -91,18 +91,14 @@ export const CollectionPanel = withStyles(styles)(
                 const { classes, item, dispatch, isWritable } = this.props;
                 return item
                     ? <>
-                        <Card className={classes.card}>
+                        <Card data-cy='collection-info-panel' className={classes.card}>
                             <CardHeader
                                 avatar={
                                     <IconButton onClick={this.openCollectionDetails}>
                                         <CollectionIcon className={classes.iconHeader} />
                                     </IconButton>
                                 }
-                                action={<div>
-                                    {isWritable === false &&
-                                    <Tooltip title="This collection is read-only">
-                                        <ReadOnlyIcon />
-                                    </Tooltip>}
+                                action={
                                     <Tooltip title="More options" disableFocusListener>
                                         <IconButton
                                             aria-label="More options"
@@ -110,8 +106,14 @@ export const CollectionPanel = withStyles(styles)(
                                             <MoreOptionsIcon />
                                         </IconButton>
                                     </Tooltip>
-                                </div>}
-                                title={<span><IllegalNamingWarning name={item.name}/>{item.name}</span>}
+                                }
+                                title={
+                                    <span>
+                                        <IllegalNamingWarning name={item.name}/>
+                                        {item.name}
+                                        {isWritable || <Chip variant="outlined" icon={<ReadOnlyIcon />} label="Read-only"/>}
+                                    </span>
+                                }
                                 titleTypographyProps={this.titleProps}
                                 subheader={item.description}
                                 subheaderTypographyProps={this.titleProps} />
@@ -171,7 +173,7 @@ export const CollectionPanel = withStyles(styles)(
                                 </Grid>
                             </CardContent>
                         </Card>
-                        <div className={classes.card}>
+                        <div className={classes.card} data-cy="collection-files-panel">
                             <CollectionPanelFiles isWritable={isWritable} />
                         </div>
                     </>

commit ee9dac4509683f2fbe5fda146bd9c18f4b765337
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Mon May 11 16:50:21 2020 -0300

    16118: Adds collection's integration test suite (WIP)
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/cypress/integration/collection-panel.spec.js b/cypress/integration/collection-panel.spec.js
new file mode 100644
index 00000000..287bfe10
--- /dev/null
+++ b/cypress/integration/collection-panel.spec.js
@@ -0,0 +1,48 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+describe('Collection panel tests', function() {
+    let activeUser;
+    let adminUser;
+
+    before(function() {
+        // Only set up common users once. These aren't set up as aliases because
+        // aliases are cleaned up after every test. Also it doesn't make sense
+        // to set the same users on beforeEach() over and over again, so we
+        // separate a little from Cypress' 'Best Practices' here.
+        cy.getUser('admin', 'Admin', 'User', true, true)
+            .as('adminUser').then(function() {
+                adminUser = this.adminUser;
+            }
+        );
+        cy.getUser('collectionuser1', 'Collection', 'User', false, true)
+            .as('activeUser').then(function() {
+                activeUser = this.activeUser;
+            }
+        );
+    })
+
+    beforeEach(function() {
+        cy.clearCookies()
+        cy.clearLocalStorage()
+    })
+
+    it('shows a collection by URL', function() {
+        cy.loginAs(activeUser);
+        cy.createCollection(adminUser.token, {
+            name: 'Test collection',
+            owner_uuid: activeUser.user.uuid,
+            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"})
+        .as('testCollection').then(function() {
+            cy.visit(`/collections/${this.testCollection.uuid}`);
+            cy.get('[data-cy=collection-info-panel]')
+                .should('contain', this.testCollection.name)
+                .and('contain', this.testCollection.uuid);
+            cy.get('[data-cy=collection-files-panel]')
+                .should('contain', 'bar');
+        })
+    })
+
+    // it('')
+})
\ No newline at end of file
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index 68ce6870..8c6fd462 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -92,3 +92,25 @@ Cypress.Commands.add(
         })
     }
 )
+
+Cypress.Commands.add(
+    "createCollection", (token, collection) => {
+        return cy.do_request('POST', '/arvados/v1/collections', {
+            collection: JSON.stringify(collection),
+            ensure_unique_name: true
+        }, null, token, true)
+        .its('body').as('collection')
+        .then(function() {
+            return this.collection;
+        })
+    }
+)
+
+Cypress.Commands.add(
+    "loginAs", (user) => {
+        cy.visit(`/token/?api_token=${user.token}`);
+        cy.url().should('contain', '/projects/');
+        cy.get('div#root').should('contain', 'Arvados Workbench (zzzzz)');
+        cy.get('div#root').should('not.contain', 'Your account is inactive');
+    }
+)
\ No newline at end of file

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list