mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-23 23:57:55 -05:00
commit
2f788186d4
18 changed files with 474 additions and 349 deletions
|
@ -1,197 +0,0 @@
|
|||
version: 2.1
|
||||
|
||||
aliases:
|
||||
- &defaults
|
||||
docker:
|
||||
- image: cimg/node:16.14.2-browsers
|
||||
auth:
|
||||
username: $DOCKERHUB_USERNAME
|
||||
password: $DOCKERHUB_PASSWORD
|
||||
working_directory: ~/repo
|
||||
- &setup
|
||||
name: "setup"
|
||||
command: |
|
||||
npm --production=false ci
|
||||
mkdir ./test/results
|
||||
- &lint
|
||||
name: "run lint tests"
|
||||
command: |
|
||||
npm run test:lint:ci
|
||||
- &build
|
||||
name: "run npm build"
|
||||
command: |
|
||||
WWW_VERSION=${CIRCLE_SHA1:0:5} npm run build
|
||||
- &unit
|
||||
name: "Run unit tests"
|
||||
command: |
|
||||
JEST_JUNIT_OUTPUT_NAME=unit-jest-results.xml npm run test:unit:jest:unit -- --reporters=jest-junit
|
||||
JEST_JUNIT_OUTPUT_NAME=localization-jest-results.xml npm run test:unit:jest:localization -- --reporters=jest-junit
|
||||
npm run test:unit:tap -- --output-file ./test/results/unit-raw.tap
|
||||
npm run test:unit:convertReportToXunit
|
||||
- &setup_python
|
||||
name: "setup python"
|
||||
command: |
|
||||
curl https://bootstrap.pypa.io/pip/3.5/get-pip.py -o get-pip.py
|
||||
python3 get-pip.py pip==21.0.1
|
||||
pip install s3cmd==2.1.0
|
||||
- &deploy
|
||||
name: "deploy"
|
||||
command: |
|
||||
npm run deploy
|
||||
- &integration
|
||||
name: "integration tests with Jest"
|
||||
command: |
|
||||
JEST_JUNIT_OUTPUT_NAME=integration-jest-results.xml npm run test:integration:remote -- --reporters=jest-junit
|
||||
- &build_no_deploy
|
||||
<<: *defaults
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
<<: *setup
|
||||
- run:
|
||||
<<: *lint
|
||||
- run:
|
||||
<<: *build
|
||||
- run:
|
||||
<<: *unit
|
||||
- store_test_results:
|
||||
path: test/results
|
||||
- &build_and_deploy
|
||||
<<: *defaults
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
<<: *setup
|
||||
- run:
|
||||
<<: *lint
|
||||
- run:
|
||||
<<: *build
|
||||
- run:
|
||||
<<: *unit
|
||||
- run:
|
||||
<<: *setup_python
|
||||
- run:
|
||||
<<: *deploy
|
||||
- store_test_results:
|
||||
path: test/results
|
||||
- run:
|
||||
name: Compress Artifacts
|
||||
command: tar -cvzf build.tar build
|
||||
- store_artifacts:
|
||||
path: build.tar
|
||||
- &integration_tests_and_store
|
||||
<<: *defaults
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
<<: *setup
|
||||
- run:
|
||||
<<: *integration
|
||||
- store_test_results:
|
||||
path: test/results
|
||||
- &update-translations
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: "setup"
|
||||
command: npm --production=false ci
|
||||
- run:
|
||||
name: "run i18n script"
|
||||
command: npm run i18n:push
|
||||
|
||||
# build-test-deploy requires two separately named jobs
|
||||
jobs:
|
||||
build-and-deploy-staging:
|
||||
<<: *build_and_deploy
|
||||
build-and-deploy-production:
|
||||
<<: *build_and_deploy
|
||||
integration-tests:
|
||||
<<: *integration_tests_and_store
|
||||
update-translations:
|
||||
<<: *update-translations
|
||||
build-no-deploy:
|
||||
<<: *build_no_deploy
|
||||
|
||||
workflows:
|
||||
build-test-deploy:
|
||||
jobs:
|
||||
- build-and-deploy-staging:
|
||||
context:
|
||||
- scratch-www-all
|
||||
- scratch-www-staging
|
||||
- dockerhub-credentials
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
- beta
|
||||
- /^hotfix\/.*/
|
||||
- /^release\/.*/
|
||||
- integration-tests:
|
||||
requires:
|
||||
- build-and-deploy-staging
|
||||
context:
|
||||
- scratch-www-all
|
||||
- scratch-www-staging
|
||||
- dockerhub-credentials
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
- beta
|
||||
- /^hotfix\/.*/
|
||||
- /^release\/.*/
|
||||
- build-and-deploy-production:
|
||||
context:
|
||||
- scratch-www-all
|
||||
- scratch-www-production
|
||||
- dockerhub-credentials
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- integration-tests:
|
||||
requires:
|
||||
- build-and-deploy-production
|
||||
context:
|
||||
- scratch-www-all
|
||||
- scratch-www-production
|
||||
- dockerhub-credentials
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
Update-translations:
|
||||
triggers:
|
||||
- schedule: # every evening at 7pm EST (8pm EDT, Midnight UTC)
|
||||
cron: "0 0 * * *"
|
||||
filters:
|
||||
branches:
|
||||
only: develop
|
||||
jobs:
|
||||
- update-translations:
|
||||
context:
|
||||
- scratch-www-all
|
||||
- scratch-www-staging
|
||||
- dockerhub-credentials
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
build-test-no-deploy:
|
||||
jobs:
|
||||
- build-no-deploy:
|
||||
context:
|
||||
- dockerhub-credentials
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- develop
|
||||
- master
|
||||
- beta
|
||||
- /^hotfix\/.*/
|
||||
- /^release\/.*/
|
138
.github/workflows/ci-cd.yml
vendored
Normal file
138
.github/workflows/ci-cd.yml
vendored
Normal file
|
@ -0,0 +1,138 @@
|
|||
name: CI/CD
|
||||
|
||||
on:
|
||||
pull_request: # Runs whenever a pull request is created or updated
|
||||
push: # Runs whenever a commit is pushed to the repository
|
||||
branches: [master, develop, beta, hotfix/*] # ...on any of these branches
|
||||
workflow_dispatch: # Allows you to run this workflow manually from the Actions tab
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CXX: g++-4.8
|
||||
FASTLY_ACTIVATE_CHANGES: true
|
||||
FASTLY_PURGE_ALL: true
|
||||
NODE_ENV: production
|
||||
SKIP_CLEANUP: true
|
||||
|
||||
jobs:
|
||||
build-and-test-and-maybe-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment: >-
|
||||
${{
|
||||
(
|
||||
(github.ref == 'refs/heads/master') && 'production'
|
||||
) ||
|
||||
(
|
||||
(
|
||||
(github.ref == 'refs/heads/develop') ||
|
||||
(github.ref == 'refs/heads/beta') ||
|
||||
startsWith(github.ref, 'refs/heads/hotfix/') ||
|
||||
startsWith(github.ref, 'refs/heads/release/')
|
||||
) && 'staging'
|
||||
) ||
|
||||
''
|
||||
}}
|
||||
env:
|
||||
# SCRATCH_ENV comes from the GitHub Environment
|
||||
# See https://github.com/scratchfoundation/scratch-www/settings/variables/actions
|
||||
SCRATCH_SHOULD_DEPLOY: ${{ vars.SCRATCH_ENV != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: 'npm'
|
||||
node-version-file: '.nvmrc'
|
||||
- name: info
|
||||
run: |
|
||||
echo "Scratch environment: ${{ vars.SCRATCH_ENV }}"
|
||||
echo "Node version: $(node --version)"
|
||||
echo "NPM version: $(npm --version)"
|
||||
- name: setup
|
||||
run: |
|
||||
npm --production=false ci
|
||||
mkdir -p ./test/results
|
||||
- name: lint
|
||||
run: npm run test:lint:ci
|
||||
- name: build
|
||||
run: WWW_VERSION=${GITHUB_SHA:0:5} npm run build
|
||||
env:
|
||||
# webpack.config.js uses these with `DefinePlugin`
|
||||
API_HOST: ${{ secrets.API_HOST }}
|
||||
RECAPTCHA_SITE_KEY: ${{ secrets.RECAPTCHA_SITE_KEY }}
|
||||
ASSET_HOST: ${{ secrets.ASSET_HOST }}
|
||||
BACKPACK_HOST: ${{ secrets.BACKPACK_HOST }}
|
||||
CLOUDDATA_HOST: ${{ secrets.CLOUDDATA_HOST }}
|
||||
PROJECT_HOST: ${{ secrets.PROJECT_HOST }}
|
||||
STATIC_HOST: ${{ secrets.STATIC_HOST }}
|
||||
SCRATCH_ENV: ${{ vars.SCRATCH_ENV }}
|
||||
|
||||
# used by src/template-config.js
|
||||
GTM_ID: ${{ secrets.GTM_ID }}
|
||||
GTM_ENV_AUTH: ${{ secrets.GTM_ENV_AUTH }}
|
||||
- name: unit tests
|
||||
run: |
|
||||
JEST_JUNIT_OUTPUT_NAME=unit-jest-results.xml npm run test:unit:jest:unit -- --reporters=jest-junit
|
||||
JEST_JUNIT_OUTPUT_NAME=localization-jest-results.xml npm run test:unit:jest:localization -- --reporters=jest-junit
|
||||
npm run test:unit:tap -- --output-file ./test/results/unit-raw.tap
|
||||
npm run test:unit:convertReportToXunit
|
||||
- name: setup Python
|
||||
if: ${{ env.SCRATCH_SHOULD_DEPLOY == 'true' }}
|
||||
run: |
|
||||
curl https://bootstrap.pypa.io/pip/3.5/get-pip.py -o get-pip.py
|
||||
python3 get-pip.py pip==21.0.1
|
||||
pip install s3cmd==2.3.0
|
||||
- name: deploy
|
||||
if: ${{ env.SCRATCH_SHOULD_DEPLOY == 'true' }}
|
||||
run: npm run deploy
|
||||
env:
|
||||
S3_LOCAL_DIR: build
|
||||
S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
FASTLY_API_KEY: ${{ secrets.FASTLY_API_KEY }}
|
||||
FASTLY_SERVICE_ID: ${{ secrets.FASTLY_SERVICE_ID }}
|
||||
SLACK_WEBHOOK_CIRCLECI_NOTIFICATIONS: ${{ secrets.SLACK_WEBHOOK_CIRCLECI_NOTIFICATIONS }} # TODO: rename or replace
|
||||
SLACK_WEBHOOK_ENGINEERING: ${{ secrets.SLACK_WEBHOOK_ENGINEERING }}
|
||||
SLACK_WEBHOOK_MODS: ${{ secrets.SLACK_WEBHOOK_MODS }}
|
||||
- name: integration tests
|
||||
if: ${{ env.SCRATCH_SHOULD_DEPLOY == 'true' }}
|
||||
run: |
|
||||
# if the health test fails, there's no point in trying to run the integration tests
|
||||
npm run test:health
|
||||
# health test succeeded, so proceed with integration tests
|
||||
JEST_JUNIT_OUTPUT_NAME=integration-jest-results.xml npm run test:integration -- --reporters=jest-junit
|
||||
env:
|
||||
ROOT_URL: ${{ secrets.ROOT_URL }}
|
||||
|
||||
# test/integration-legacy/selenium-helpers.js
|
||||
CI: "true"
|
||||
CIRCLECI: "true" # TODO
|
||||
CIRCLE_BUILD_NUM: ${{ github.run_id }} # TODO
|
||||
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
|
||||
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
|
||||
SMOKE_REMOTE: "true" # use Sauce Labs
|
||||
|
||||
# test/integration/*
|
||||
SMOKE_USERNAME: ${{ secrets.SMOKE_USERNAME }}
|
||||
SMOKE_PASSWORD: ${{ secrets.SMOKE_PASSWORD }}
|
||||
COMMENT_PROJECT_ID: ${{ secrets.COMMENT_PROJECT_ID }}
|
||||
COMMENT_STUDIO_ID: ${{ secrets.COMMENT_STUDIO_ID }}
|
||||
UNOWNED_SHARED_PROJECT_ID: ${{ secrets.UNOWNED_SHARED_PROJECT_ID }}
|
||||
OWNED_SHARED_PROJECT_ID: ${{ secrets.OWNED_SHARED_PROJECT_ID }}
|
||||
OWNED_UNSHARED_PROJECT_ID: ${{ secrets.OWNED_UNSHARED_PROJECT_ID }}
|
||||
UNOWNED_UNSHARED_PROJECT_ID: ${{ secrets.UNOWNED_UNSHARED_PROJECT_ID }}
|
||||
UNOWNED_SHARED_SCRATCH2_PROJECT_ID: ${{ secrets.UNOWNED_SHARED_SCRATCH2_PROJECT_ID }}
|
||||
OWNED_UNSHARED_SCRATCH2_PROJECT_ID: ${{ secrets.OWNED_UNSHARED_SCRATCH2_PROJECT_ID }}
|
||||
TEST_STUDIO_ID: ${{ secrets.TEST_STUDIO_ID }}
|
||||
RATE_LIMIT_CHECK: ${{ secrets.RATE_LIMIT_CHECK }}
|
||||
- name: compress artifact
|
||||
if: ${{ env.SCRATCH_SHOULD_DEPLOY == 'true' }}
|
||||
run: tar -czvf build.tgz build
|
||||
- name: upload artifact
|
||||
if: ${{ env.SCRATCH_SHOULD_DEPLOY == 'true' }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: build.tgz
|
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
@ -0,0 +1 @@
|
|||
v16
|
|
@ -7,8 +7,8 @@
|
|||
"test": "npm run test:lint && npm run build && npm run test:unit",
|
||||
"test:lint": "eslint . --ext .js,.jsx,.json",
|
||||
"test:lint:ci": "eslint . --ext .js,.jsx,.json --format junit -o ./test/results/lint-results.xml",
|
||||
"test:health": "jest ./test/health/*.test.js",
|
||||
"test:integration": "jest ./test/integration/*.test.js --reporters=default --maxWorkers=5",
|
||||
"test:integration:remote": "SMOKE_REMOTE=true jest ./test/integration/*.test.js --reporters=default --maxWorkers=5",
|
||||
"test:unit": "npm run test:unit:jest && npm run test:unit:tap",
|
||||
"test:unit:jest": "npm run test:unit:jest:unit && npm run test:unit:jest:localization",
|
||||
"test:unit:jest:unit": "jest ./test/unit/ --reporters=default",
|
||||
|
|
51
test/health/server-health.test.js
Normal file
51
test/health/server-health.test.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
/* eslint-disable no-console */
|
||||
|
||||
// this basic server health check is meant to be run before integration tests
|
||||
// it should be run with the same environment variables as the integration tests
|
||||
// and operate in the same way as the integration tests
|
||||
|
||||
const SeleniumHelper = require('../integration/selenium-helpers.js');
|
||||
|
||||
const rootUrl = process.env.ROOT_URL || (() => {
|
||||
const ROOT_URL_DEFAULT = 'https://scratch.ly';
|
||||
console.warn(`ROOT_URL not set, defaulting to ${ROOT_URL_DEFAULT}`);
|
||||
return ROOT_URL_DEFAULT;
|
||||
})();
|
||||
|
||||
jest.setTimeout(60000);
|
||||
|
||||
describe('www server health check', () => {
|
||||
/** @type {import('selenium-webdriver').ThenableWebDriver} */
|
||||
let driver;
|
||||
|
||||
/** @type {SeleniumHelper} */
|
||||
let seleniumHelper;
|
||||
|
||||
beforeAll(() => {
|
||||
seleniumHelper = new SeleniumHelper();
|
||||
driver = seleniumHelper.buildDriver('www server health check');
|
||||
});
|
||||
|
||||
afterAll(() => driver.quit());
|
||||
|
||||
test('server is healthy', async () => {
|
||||
const healthUrl = new URL('health/', rootUrl);
|
||||
await driver.get(healthUrl.toString());
|
||||
|
||||
// Note: driver.getPageSource() will return the pretty HTML form of the JSON
|
||||
const pageText = await driver.executeScript('return document.body.innerText');
|
||||
|
||||
let healthObject;
|
||||
let serverReturnedValidJson = false;
|
||||
|
||||
try {
|
||||
healthObject = JSON.parse(pageText);
|
||||
serverReturnedValidJson = true;
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
expect(serverReturnedValidJson).toBe(true);
|
||||
expect(healthObject).toHaveProperty('healthy', true);
|
||||
});
|
||||
});
|
|
@ -8,6 +8,7 @@ const {
|
|||
clickXpath,
|
||||
containsClass,
|
||||
findByXpath,
|
||||
navigate,
|
||||
signIn
|
||||
} = new SeleniumHelper();
|
||||
|
||||
|
@ -29,13 +30,12 @@ const studioId = process.env.COMMENT_STUDIO_ID || 10005646;
|
|||
const studioUrl = `${rootUrl}/studios/${studioId}/comments`;
|
||||
|
||||
// setup comments to leave
|
||||
// make sure they are unique and will not be censored (avoid numbers that might look like phone numbers or other PII)
|
||||
const date = new Date();
|
||||
const dateString = `Y:${date.getFullYear()} - M:${date.getMonth() + 1} - D:${date.getDate()} ` +
|
||||
`: ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
|
||||
const buildNumber = process.env.CIRCLE_BUILD_NUM || dateString;
|
||||
const projectComment = `${buildNumber} project`;
|
||||
const profileComment = `${buildNumber} profile`;
|
||||
const studioComment = `${buildNumber} studio`;
|
||||
const dateString = date.toISOString();
|
||||
const projectComment = `${dateString} project`;
|
||||
const profileComment = `${dateString} profile`;
|
||||
const studioComment = `${dateString} studio`;
|
||||
|
||||
const projectReply = `${projectComment} reply`;
|
||||
const profileReply = `${profileComment} reply`;
|
||||
|
@ -48,7 +48,7 @@ let driver;
|
|||
describe('comment tests', () => {
|
||||
beforeAll(async () => {
|
||||
driver = await buildDriver('www-integration project comments');
|
||||
await driver.get(rootUrl);
|
||||
await navigate(rootUrl);
|
||||
});
|
||||
|
||||
afterAll(() => driver.quit());
|
||||
|
@ -60,13 +60,13 @@ describe('comment tests', () => {
|
|||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await driver.get(rootUrl);
|
||||
await navigate(rootUrl);
|
||||
await clickXpath('//a[contains(@class, "user-info")]');
|
||||
await clickText('Sign out');
|
||||
});
|
||||
|
||||
test('leave comment on project', async () => {
|
||||
await driver.get(projectUrl);
|
||||
await navigate(projectUrl);
|
||||
|
||||
// leave the comment
|
||||
const commentBox = await findByXpath('//textArea[@name="compose-comment"]');
|
||||
|
@ -82,7 +82,7 @@ describe('comment tests', () => {
|
|||
});
|
||||
|
||||
test('leave comment on a profile', async () => {
|
||||
await driver.get(profileUrl);
|
||||
await navigate(profileUrl);
|
||||
|
||||
// leave the comment
|
||||
const commentXpath = '//form[@id="main-post-form"]/div/textArea';
|
||||
|
@ -97,11 +97,11 @@ describe('comment tests', () => {
|
|||
expect(commentVisible).toBe(true);
|
||||
|
||||
// return to homepage to sign out with www
|
||||
await driver.get(rootUrl);
|
||||
await navigate(rootUrl);
|
||||
});
|
||||
|
||||
test('leave comment on studio', async () => {
|
||||
await driver.get(studioUrl);
|
||||
await navigate(studioUrl);
|
||||
|
||||
// leave the comment
|
||||
const commentBox = await findByXpath('//textArea[@name="compose-comment"]');
|
||||
|
@ -138,7 +138,7 @@ describe('comment tests', () => {
|
|||
});
|
||||
|
||||
test('project comment message visible', async () => {
|
||||
await driver.get(`${rootUrl}/messages`);
|
||||
await navigate(`${rootUrl}/messages`);
|
||||
|
||||
const projectMessageXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
`and contains(text(), "${projectComment}")]`;
|
||||
|
@ -148,7 +148,7 @@ describe('comment tests', () => {
|
|||
});
|
||||
|
||||
test('profile comment message visible', async () => {
|
||||
await driver.get(`${rootUrl}/messages`);
|
||||
await navigate(`${rootUrl}/messages`);
|
||||
|
||||
const profileMessageXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
`and contains(text(), "${profileComment}")]`;
|
||||
|
@ -163,7 +163,7 @@ describe('comment tests', () => {
|
|||
const projectLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
`and contains(text(), "${projectComment}")]/../../../p[@class = "comment-message-info"]/span/a[2]`;
|
||||
|
||||
await driver.get(`${rootUrl}/messages`);
|
||||
await navigate(`${rootUrl}/messages`);
|
||||
await clickXpath(projectLinkXpath);
|
||||
|
||||
// find green flag overlay
|
||||
|
@ -175,7 +175,7 @@ describe('comment tests', () => {
|
|||
const projectLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
`and contains(text(), "${projectComment}")]/../../../p[@class = "comment-message-info"]/span/a[2]`;
|
||||
|
||||
await driver.get(`${rootUrl}/messages`);
|
||||
await navigate(`${rootUrl}/messages`);
|
||||
await clickXpath(projectLinkXpath);
|
||||
|
||||
const commentXpath = `//span[contains(text(), "${projectComment}")]`;
|
||||
|
@ -189,7 +189,7 @@ describe('comment tests', () => {
|
|||
`and contains(text(), "${projectComment}")]/../../../p[@class = "comment-message-info"]/span/a[2]`;
|
||||
const containerXpath = `//span[contains(text(), "${projectComment}")]/../../../..`;
|
||||
|
||||
await driver.get(`${rootUrl}/messages`);
|
||||
await navigate(`${rootUrl}/messages`);
|
||||
await clickXpath(projectLinkXpath);
|
||||
|
||||
const commentContainer = await findByXpath(containerXpath);
|
||||
|
@ -201,7 +201,7 @@ describe('comment tests', () => {
|
|||
const profileLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
`and contains(text(), "${profileComment}")]/../../../` +
|
||||
'p[@class = "comment-message-info"]/span/a[2]';
|
||||
await driver.get(`${rootUrl}/messages`);
|
||||
await navigate(`${rootUrl}/messages`);
|
||||
await clickXpath(profileLinkXpath);
|
||||
|
||||
// find profile data
|
||||
|
@ -218,7 +218,7 @@ describe('comment tests', () => {
|
|||
const profileLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
`and contains(text(), "${profileComment}")]/../../../` +
|
||||
'p[@class = "comment-message-info"]/span/a[2]';
|
||||
await driver.get(`${rootUrl}/messages`);
|
||||
await navigate(`${rootUrl}/messages`);
|
||||
await clickXpath(profileLinkXpath);
|
||||
|
||||
// find comment
|
||||
|
@ -233,7 +233,7 @@ describe('comment tests', () => {
|
|||
const profileLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||
`and contains(text(), "${profileComment}")]/../../../` +
|
||||
'p[@class = "comment-message-info"]/span/a[2]';
|
||||
await driver.get(`${rootUrl}/messages`);
|
||||
await navigate(`${rootUrl}/messages`);
|
||||
await clickXpath(profileLinkXpath);
|
||||
|
||||
// comment highlighted?
|
||||
|
@ -244,7 +244,7 @@ describe('comment tests', () => {
|
|||
});
|
||||
|
||||
test('project: reply to comment', async () => {
|
||||
await driver.get(projectUrl);
|
||||
await navigate(projectUrl);
|
||||
const commentXpath = `//span[contains(text(), "${projectComment}")]/../..`;
|
||||
const replyXpath = `${commentXpath}//span[@class = "comment-reply"]`;
|
||||
await clickXpath(replyXpath);
|
||||
|
@ -256,8 +256,7 @@ describe('comment tests', () => {
|
|||
await composeBox.sendKeys(projectReply);
|
||||
|
||||
// click post
|
||||
const postButton = await findByXpath(`${replyRow}//button[@class = "button compose-post"]`);
|
||||
await postButton.click();
|
||||
await clickXpath(`${replyRow}//button[@class = "button compose-post"]`);
|
||||
|
||||
const postedReply = await findByXpath(`//span[contains(text(), "${projectReply}")]`);
|
||||
const commentVisible = await postedReply.isDisplayed();
|
||||
|
@ -265,7 +264,7 @@ describe('comment tests', () => {
|
|||
});
|
||||
|
||||
test('profile reply to comment', async () => {
|
||||
await driver.get(profileUrl);
|
||||
await navigate(profileUrl);
|
||||
// find the comment and click reply
|
||||
const commentXpath = `//div[contains(text(), "${profileComment}")]/..`;
|
||||
await clickXpath(`${commentXpath}//a[@class = "reply"]`);
|
||||
|
@ -282,7 +281,7 @@ describe('comment tests', () => {
|
|||
});
|
||||
|
||||
test('studio: reply to comment', async () => {
|
||||
await driver.get(studioUrl);
|
||||
await navigate(studioUrl);
|
||||
|
||||
// find the comment and click reply
|
||||
const commentXpath = `//span[contains(text(), "${studioComment}")]/../..`;
|
||||
|
|
|
@ -5,7 +5,9 @@ const SeleniumHelper = require('./selenium-helpers.js');
|
|||
const {
|
||||
clickText,
|
||||
buildDriver,
|
||||
findText
|
||||
findText,
|
||||
navigate,
|
||||
waitUntilDocumentReady
|
||||
} = new SeleniumHelper();
|
||||
|
||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
|
@ -20,7 +22,7 @@ describe('www-integration footer links', () => {
|
|||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(rootUrl);
|
||||
await navigate(rootUrl);
|
||||
await findText('Create stories, games, and animations');
|
||||
});
|
||||
|
||||
|
@ -30,6 +32,7 @@ describe('www-integration footer links', () => {
|
|||
|
||||
test('click About Scratch link', async () => {
|
||||
await clickText('About Scratch');
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/about\/?$/);
|
||||
|
@ -37,6 +40,7 @@ describe('www-integration footer links', () => {
|
|||
|
||||
test('click For Parents link', async () => {
|
||||
await clickText('For Parents');
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/parents\/?$/);
|
||||
|
@ -44,6 +48,7 @@ describe('www-integration footer links', () => {
|
|||
|
||||
test('click For Educators link', async () => {
|
||||
await clickText('For Educators');
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/educators\/?$/);
|
||||
|
@ -51,6 +56,7 @@ describe('www-integration footer links', () => {
|
|||
|
||||
test('click For Developers link', async () => {
|
||||
await clickText('For Developers');
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/developers\/?$/);
|
||||
|
@ -60,6 +66,7 @@ describe('www-integration footer links', () => {
|
|||
|
||||
test('click Community Guidelines link', async () => {
|
||||
await clickText('Community Guidelines');
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/community_guidelines\/?$/);
|
||||
|
@ -67,6 +74,7 @@ describe('www-integration footer links', () => {
|
|||
|
||||
test('click Discussion Forums link', async () => {
|
||||
await clickText('Discussion Forums');
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/discuss\/?$/);
|
||||
|
@ -74,6 +82,7 @@ describe('www-integration footer links', () => {
|
|||
|
||||
test('click Statistics link', async () => {
|
||||
await clickText('Statistics');
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/statistics\/?$/);
|
||||
|
@ -83,6 +92,7 @@ describe('www-integration footer links', () => {
|
|||
|
||||
test('click Ideas link', async () => {
|
||||
await clickText('Ideas');
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/ideas\/?$/);
|
||||
|
@ -90,6 +100,7 @@ describe('www-integration footer links', () => {
|
|||
|
||||
test('click FAQ link', async () => {
|
||||
await clickText('FAQ');
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/faq\/?$/);
|
||||
|
@ -97,6 +108,7 @@ describe('www-integration footer links', () => {
|
|||
|
||||
test('click Download link', async () => {
|
||||
await clickText('Download');
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/download\/?$/);
|
||||
|
@ -104,6 +116,7 @@ describe('www-integration footer links', () => {
|
|||
|
||||
test('click Contact Us link', async () => {
|
||||
await clickText('Contact Us');
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/contact-us\/?$/);
|
||||
|
@ -113,6 +126,7 @@ describe('www-integration footer links', () => {
|
|||
|
||||
test('click Terms of Use link', async () => {
|
||||
await clickText('Terms of Use');
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/terms_of_use\/?$/);
|
||||
|
@ -120,6 +134,7 @@ describe('www-integration footer links', () => {
|
|||
|
||||
test('click Privacy Policy link', async () => {
|
||||
await clickText('Privacy Policy');
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/privacy_policy\/?$/);
|
||||
|
@ -127,6 +142,7 @@ describe('www-integration footer links', () => {
|
|||
|
||||
test('click Cookies link', async () => {
|
||||
await clickText('Cookies');
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/cookies\/?$/);
|
||||
|
@ -139,23 +155,14 @@ describe('www-integration footer links', () => {
|
|||
|
||||
test('click DMCA link', async () => {
|
||||
await clickText('DMCA');
|
||||
await waitUntilDocumentReady();
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/DMCA\/?$/);
|
||||
});
|
||||
|
||||
// ==== SCRATCH FAMILY column ====
|
||||
|
||||
test('click Scratch Conference link', async () => {
|
||||
await clickText('Scratch Conference');
|
||||
const url = await driver.getCurrentUrl();
|
||||
const pathname = (new URL(url)).pathname;
|
||||
expect(pathname).toMatch(/^\/scratch-conference\/?$/);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// The following links in are skipped because they are not on scratch.mit.edu
|
||||
// The following links in the footer are skipped because they are not part of scratch-www
|
||||
|
||||
// Jobs
|
||||
// Press
|
||||
|
@ -166,3 +173,4 @@ describe('www-integration footer links', () => {
|
|||
// SCRATCH JR (SCRATCHJR)
|
||||
// SCRATCH DAY
|
||||
// SCRATCH FOUNDATION
|
||||
// Scratch Conference
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
const SeleniumHelper = require('./selenium-helpers.js');
|
||||
|
||||
const {
|
||||
buildDriver,
|
||||
clickXpath,
|
||||
findByXpath,
|
||||
buildDriver
|
||||
navigate,
|
||||
waitUntilDocumentReady
|
||||
} = new SeleniumHelper();
|
||||
|
||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
|
@ -17,11 +19,11 @@ let driver;
|
|||
describe('www-integration project rows', () => {
|
||||
beforeAll(async () => {
|
||||
driver = await buildDriver('www-integration project rows');
|
||||
// driver.get(rootUrl);
|
||||
// navigate(rootUrl);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(rootUrl);
|
||||
await navigate(rootUrl);
|
||||
});
|
||||
|
||||
afterAll(() => driver.quit());
|
||||
|
@ -49,6 +51,7 @@ describe('www-integration project rows', () => {
|
|||
test('Featured Studios link', async () => {
|
||||
await clickXpath('//div[@class="box"][descendant::text()="Featured Studios"]' +
|
||||
'//div[contains(@class, "thumbnail")][1]/a[@class="thumbnail-image"]');
|
||||
await waitUntilDocumentReady();
|
||||
const studioInfo = await findByXpath('//div[contains(@class, "studio-info")]');
|
||||
const studioInfoDisplayed = await studioInfo.isDisplayed();
|
||||
expect(studioInfoDisplayed).toBe(true);
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
const SeleniumHelper = require('./selenium-helpers.js');
|
||||
|
||||
const {
|
||||
findByXpath,
|
||||
buildDriver,
|
||||
clickXpath,
|
||||
buildDriver
|
||||
findByXpath,
|
||||
navigate,
|
||||
waitUntilDocumentReady
|
||||
} = new SeleniumHelper();
|
||||
|
||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
|
@ -18,14 +20,14 @@ let driver;
|
|||
describe('www-integration join flow', () => {
|
||||
beforeAll(async () => {
|
||||
driver = await buildDriver('www-integration join flow');
|
||||
await driver.get(rootUrl);
|
||||
});
|
||||
|
||||
afterAll(() => driver.quit());
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(rootUrl);
|
||||
await clickXpath('//a[@class="registrationLink"]');
|
||||
await navigate(rootUrl); // navigate to home page
|
||||
await clickXpath('//a[@class="registrationLink"]'); // navigate to join page
|
||||
await waitUntilDocumentReady();
|
||||
});
|
||||
|
||||
test('click Join opens join modal', async () => {
|
||||
|
@ -35,22 +37,24 @@ describe('www-integration join flow', () => {
|
|||
});
|
||||
|
||||
test('username validation message appears', async () => {
|
||||
await clickXpath('//input[contains(@name, "username")]');
|
||||
const clickedInput = await clickXpath('//input[contains(@name, "username")]');
|
||||
await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', clickedInput));
|
||||
const message = await findByXpath('//div[contains(@class, "validation-message")]');
|
||||
const messageText = await message.getText();
|
||||
expect(messageText).toEqual('Don\'t use your real name');
|
||||
|
||||
});
|
||||
|
||||
test('password validation message appears', async () => {
|
||||
await clickXpath('//input[contains(@name, "password")]');
|
||||
const clickedInput = await clickXpath('//input[contains(@name, "password")]');
|
||||
await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', clickedInput));
|
||||
const message = await findByXpath('//div[contains(@class, "validation-message")]');
|
||||
const messageText = await message.getText();
|
||||
expect(messageText).toContain('Write it down so you remember.');
|
||||
});
|
||||
|
||||
test('password confirmation validation message appears', async () => {
|
||||
await clickXpath('//input[contains(@name, "passwordConfirm")]');
|
||||
const clickedInput = await clickXpath('//input[contains(@name, "passwordConfirm")]');
|
||||
await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', clickedInput));
|
||||
const message = await findByXpath('//div[contains(@class, "validation-message")]');
|
||||
const messageText = await message.getText();
|
||||
expect(messageText).toEqual('Type password again');
|
||||
|
@ -59,6 +63,7 @@ describe('www-integration join flow', () => {
|
|||
test('username validation: too short', async () => {
|
||||
const textInput = await findByXpath('//input[contains(@name, "username")]');
|
||||
await textInput.click();
|
||||
await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', textInput));
|
||||
await textInput.sendKeys('ab');
|
||||
await clickXpath('//div[@class = "join-flow-outer-content"]');
|
||||
const message = await findByXpath('//div[contains(@class, "validation-error")]');
|
||||
|
@ -69,6 +74,7 @@ describe('www-integration join flow', () => {
|
|||
test('username validation: username taken', async () => {
|
||||
const textInput = await findByXpath('//input[contains(@name, "username")]');
|
||||
await textInput.click();
|
||||
await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', textInput));
|
||||
await textInput.sendKeys(takenUsername);
|
||||
await clickXpath('//div[@class = "join-flow-outer-content"]');
|
||||
const message = await findByXpath('//div[contains(@class, "validation-error")]');
|
||||
|
@ -79,6 +85,7 @@ describe('www-integration join flow', () => {
|
|||
test('username validation: bad word', async () => {
|
||||
const textInput = await findByXpath('//input[contains(@name, "username")]');
|
||||
await textInput.click();
|
||||
await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', textInput));
|
||||
// Should be caught by the filter
|
||||
await textInput.sendKeys('xxxxxxxxx');
|
||||
await clickXpath('//div[@class = "join-flow-outer-content"]');
|
||||
|
|
|
@ -7,7 +7,10 @@ const {
|
|||
clickText,
|
||||
clickXpath,
|
||||
findByXpath,
|
||||
signIn
|
||||
navigate,
|
||||
signIn,
|
||||
urlMatches,
|
||||
waitUntilDocumentReady
|
||||
} = new SeleniumHelper();
|
||||
|
||||
const username = `${process.env.SMOKE_USERNAME}1`;
|
||||
|
@ -24,8 +27,7 @@ let driver;
|
|||
describe('www-integration my_stuff', () => {
|
||||
beforeAll(async () => {
|
||||
driver = await buildDriver('www-integration my_stuff');
|
||||
await driver.get(rootUrl);
|
||||
await driver.sleep(1000);
|
||||
await navigate(rootUrl);
|
||||
await signIn(username, password);
|
||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
});
|
||||
|
@ -33,7 +35,7 @@ describe('www-integration my_stuff', () => {
|
|||
afterAll(() => driver.quit());
|
||||
|
||||
test('verify My Stuff structure (tabs, title)', async () => {
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(myStuffURL);
|
||||
const header = await findByXpath('//div[@class="box-head"]/h2');
|
||||
const headerVisible = await header.isDisplayed();
|
||||
expect(headerVisible).toBe(true);
|
||||
|
@ -55,24 +57,25 @@ describe('www-integration my_stuff', () => {
|
|||
});
|
||||
|
||||
test('clicking a project title should take you to the project page', async () => {
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//span[@class="media-info-item title"]');
|
||||
await driver.sleep(6000);
|
||||
await waitUntilDocumentReady();
|
||||
const gui = await findByXpath('//div[@class="guiPlayer"]');
|
||||
const guiVisible = await gui.isDisplayed();
|
||||
expect(guiVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('clicking "see inside" should take you to the editor', async () => {
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//a[@data-control="edit"]');
|
||||
await waitUntilDocumentReady();
|
||||
const gf = await findByXpath('//img[@class="green-flag_green-flag_1kiAo"]');
|
||||
const gfVisible = await gf.isDisplayed();
|
||||
expect(gfVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('Add To button should bring up a list of studios', async () => {
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//div[@id="sidebar"]/ul/li[@data-tab="shared"]');
|
||||
await clickXpath('//div[@data-control="add-to"]');
|
||||
const dropDown = await findByXpath('//div[@class="dropdown-menu"]/ul/li');
|
||||
|
@ -81,52 +84,53 @@ describe('www-integration my_stuff', () => {
|
|||
});
|
||||
|
||||
test('+ New Project button should open the editor', async () => {
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(myStuffURL);
|
||||
await clickText('+ New Project');
|
||||
await waitUntilDocumentReady();
|
||||
const gf = await findByXpath('//img[@class="green-flag_green-flag_1kiAo"]');
|
||||
const gfVisible = await gf.isDisplayed();
|
||||
expect(gfVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('+ New Studio button should take you to the studio page', async () => {
|
||||
await driver.get(rateLimitCheck);
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(rateLimitCheck);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||
await waitUntilDocumentReady();
|
||||
const tabs = await findByXpath('//div[@class="studio-tabs"]');
|
||||
const tabsVisible = await tabs.isDisplayed();
|
||||
expect(tabsVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('New studio rate limited to five', async () => {
|
||||
await driver.get(rateLimitCheck);
|
||||
await navigate(rateLimitCheck);
|
||||
// 1st studio
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||
await findByXpath('//div[@class="studio-tabs"]');
|
||||
await urlMatches(/\/studios\//);
|
||||
// 2nd studio
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||
await findByXpath('//div[@class="studio-tabs"]');
|
||||
await urlMatches(/\/studios\//);
|
||||
// 3rd studio
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||
await findByXpath('//div[@class="studio-tabs"]');
|
||||
await urlMatches(/\/studios\//);
|
||||
// 4th studio
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||
await findByXpath('//div[@class="studio-tabs"]');
|
||||
await urlMatches(/\/studios\//);
|
||||
// 5th studio
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||
await findByXpath('//div[@class="studio-tabs"]');
|
||||
await urlMatches(/\/studios\//);
|
||||
// 6th studio should fail
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||
// findByXpath checks for both presence and visibility
|
||||
const alertMessage = await findByXpath('//div[contains(@class, "alert-error")]');
|
||||
const errVisible = await alertMessage.isDisplayed();
|
||||
expect(errVisible).toBe(true);
|
||||
expect(alertMessage).toBeTruthy();
|
||||
|
||||
await driver.get(rateLimitCheck);
|
||||
await navigate(rateLimitCheck);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -4,8 +4,9 @@ const SeleniumHelper = require('./selenium-helpers.js');
|
|||
|
||||
const {
|
||||
clickXpath,
|
||||
buildDriver,
|
||||
findByXpath,
|
||||
buildDriver
|
||||
navigate
|
||||
} = new SeleniumHelper();
|
||||
|
||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
|
@ -20,7 +21,7 @@ describe('www-integration navbar links', () => {
|
|||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(rootUrl);
|
||||
await navigate(rootUrl);
|
||||
});
|
||||
|
||||
afterAll(() => driver.quit());
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// some tests use projects owned by user #2
|
||||
|
||||
const SeleniumHelper = require('./selenium-helpers.js');
|
||||
const {until} = require('selenium-webdriver');
|
||||
import path from 'path';
|
||||
|
||||
const {
|
||||
|
@ -11,7 +12,9 @@ const {
|
|||
clickXpath,
|
||||
findText,
|
||||
findByXpath,
|
||||
isSignedIn,
|
||||
signIn,
|
||||
navigate,
|
||||
waitUntilVisible
|
||||
} = new SeleniumHelper();
|
||||
|
||||
|
@ -52,11 +55,11 @@ describe('www-integration project-page signed out', () => {
|
|||
beforeAll(async () => {
|
||||
// expect(projectUrl).toBe(defined);
|
||||
driver = await buildDriver('www-integration project-page signed out');
|
||||
await driver.get(rootUrl);
|
||||
await navigate(rootUrl);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(unownedSharedUrl);
|
||||
await navigate(unownedSharedUrl);
|
||||
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await waitUntilVisible(gfOverlay, driver);
|
||||
});
|
||||
|
@ -103,7 +106,7 @@ describe('www-integration project-page signed out', () => {
|
|||
|
||||
// Load an unshared project while signed out, get error
|
||||
test('Load an ushared project you do not own (error)', async () => {
|
||||
await driver.get(unownedUnsharedUrl);
|
||||
await navigate(unownedUnsharedUrl);
|
||||
const unavailableImage = await findByXpath('//img[@class="not-available-image"]');
|
||||
await waitUntilVisible(unavailableImage, driver);
|
||||
const unavailableVisible = await unavailableImage.isDisplayed();
|
||||
|
@ -117,14 +120,14 @@ describe('www-integration project-page signed in', () => {
|
|||
beforeAll(async () => {
|
||||
// expect(projectUrl).toBe(defined);
|
||||
driver = await buildDriver('www-integration project-page signed in');
|
||||
await driver.get(rootUrl);
|
||||
await driver.sleep(1000);
|
||||
await signIn(username, password);
|
||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(rootUrl);
|
||||
// The browser may or may not retain cookies between tests, depending on configuration.
|
||||
await navigate(rootUrl);
|
||||
if (!await isSignedIn()) {
|
||||
await signIn(username, password);
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(() => driver.quit());
|
||||
|
@ -133,7 +136,7 @@ describe('www-integration project-page signed in', () => {
|
|||
|
||||
// Load a shared project you own
|
||||
test('Load a shared project you own', async () => {
|
||||
await driver.get(ownedSharedUrl);
|
||||
await navigate(ownedSharedUrl);
|
||||
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await waitUntilVisible(gfOverlay, driver);
|
||||
const gfVisible = await gfOverlay.isDisplayed();
|
||||
|
@ -142,7 +145,7 @@ describe('www-integration project-page signed in', () => {
|
|||
|
||||
// Load a shared project you don't own
|
||||
test('Load a shared project you do not own', async () => {
|
||||
await driver.get(unownedSharedUrl);
|
||||
await navigate(unownedSharedUrl);
|
||||
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await waitUntilVisible(gfOverlay, driver);
|
||||
const gfVisible = await gfOverlay.isDisplayed();
|
||||
|
@ -151,7 +154,7 @@ describe('www-integration project-page signed in', () => {
|
|||
|
||||
// Load an unshared project you own
|
||||
test('Load an unshared project you own', async () => {
|
||||
await driver.get(ownedUnsharedUrl);
|
||||
await navigate(ownedUnsharedUrl);
|
||||
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await waitUntilVisible(gfOverlay, driver);
|
||||
const gfVisible = await gfOverlay.isDisplayed();
|
||||
|
@ -160,7 +163,7 @@ describe('www-integration project-page signed in', () => {
|
|||
|
||||
// Load an unshared project you don't own, get error
|
||||
test('Load an ushared project you do not own (error)', async () => {
|
||||
await driver.get(unownedUnsharedUrl);
|
||||
await navigate(unownedUnsharedUrl);
|
||||
const unavailableImage = await findByXpath('//img[@class="not-available-image"]');
|
||||
await waitUntilVisible(unavailableImage, driver);
|
||||
const unavailableVisible = await unavailableImage.isDisplayed();
|
||||
|
@ -169,7 +172,7 @@ describe('www-integration project-page signed in', () => {
|
|||
|
||||
// Load a shared scratch 2 project you don't own
|
||||
test('Load a shared scratch 2 project you do not own', async () => {
|
||||
await driver.get(unownedSharedScratch2Url);
|
||||
await navigate(unownedSharedScratch2Url);
|
||||
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await waitUntilVisible(gfOverlay, driver);
|
||||
const gfVisible = await gfOverlay.isDisplayed();
|
||||
|
@ -178,7 +181,7 @@ describe('www-integration project-page signed in', () => {
|
|||
|
||||
// Load an unshared scratch 2 project you own
|
||||
test('Load an unshared scratch 2 project you own', async () => {
|
||||
await driver.get(ownedUnsharedScratch2Url);
|
||||
await navigate(ownedUnsharedScratch2Url);
|
||||
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await waitUntilVisible(gfOverlay, driver);
|
||||
const gfVisible = await gfOverlay.isDisplayed();
|
||||
|
@ -188,32 +191,29 @@ describe('www-integration project-page signed in', () => {
|
|||
|
||||
describe('www-integration project-creation signed in', () => {
|
||||
beforeAll(async () => {
|
||||
// expect(projectUrl).toBe(defined);
|
||||
driver = await buildDriver('www-integration project-creation signed in');
|
||||
await driver.get(rootUrl);
|
||||
await driver.sleep(1000);
|
||||
await signIn(username, password);
|
||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
|
||||
// SauceLabs doesn't have access to the sb3 used in 'load project from file' test
|
||||
// https://support.saucelabs.com/hc/en-us/articles/115003685593-Uploading-Files-to-a-Sauce-Labs-Virtual-Machine-during-a-Test
|
||||
if (remote) {
|
||||
await driver.get('https://github.com/scratchfoundation/scratch-www/blob/develop/test/fixtures/project1.sb3');
|
||||
await clickXpath('//Button[@data-testid="download-raw-button"]');
|
||||
await navigate('https://github.com/scratchfoundation/scratch-www/blob/develop/test/fixtures/project1.sb3');
|
||||
await clickXpath('//button[@data-testid="download-raw-button"]');
|
||||
await driver.sleep(3000);
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(rootUrl);
|
||||
// The browser may or may not retain cookies between tests, depending on configuration.
|
||||
await navigate(rootUrl);
|
||||
if (!await isSignedIn()) {
|
||||
await signIn(username, password);
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(() => driver.quit());
|
||||
|
||||
test('make a copy of a project', async () => {
|
||||
await driver.get(`${ownedUnsharedUrl}/editor`);
|
||||
const gf = await findByXpath('//img[@class="green-flag_green-flag_1kiAo"]');
|
||||
await gf.isDisplayed();
|
||||
await navigate(`${ownedUnsharedUrl}/editor`);
|
||||
await clickXpath(FILE_MENU_XPATH);
|
||||
await clickText('Save as a copy');
|
||||
const successAlert = await findText('Project saved as a copy.');
|
||||
|
@ -226,7 +226,7 @@ describe('www-integration project-creation signed in', () => {
|
|||
});
|
||||
|
||||
test('remix a project', async () => {
|
||||
await driver.get(unownedSharedUrl);
|
||||
await navigate(unownedSharedUrl);
|
||||
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||
await waitUntilVisible(gfOverlay, driver);
|
||||
await clickXpath('//button[@class="button remix-button"]');
|
||||
|
@ -245,17 +245,17 @@ describe('www-integration project-creation signed in', () => {
|
|||
'/Users/chef/Downloads/project1.sb3' :
|
||||
path.resolve(__dirname, '../fixtures/project1.sb3');
|
||||
|
||||
// upload file
|
||||
// create a new project so there's unsaved content to trigger an alert
|
||||
await clickXpath('//li[@class="link create"]');
|
||||
const gf = await findByXpath('//img[@class="green-flag_green-flag_1kiAo"]');
|
||||
await gf.isDisplayed();
|
||||
|
||||
// upload file
|
||||
await clickXpath(FILE_MENU_XPATH);
|
||||
await clickText('Load from your computer');
|
||||
await driver.sleep(1000);
|
||||
const input = await findByXpath('//input[@accept=".sb,.sb2,.sb3"]');
|
||||
await input.sendKeys(projectPath);
|
||||
|
||||
// accept alert
|
||||
await driver.wait(until.alertIsPresent());
|
||||
const alert = await driver.switchTo().alert();
|
||||
await alert.accept();
|
||||
|
||||
|
|
|
@ -6,7 +6,8 @@ const {
|
|||
buildDriver,
|
||||
clickXpath,
|
||||
findByXpath,
|
||||
getKey
|
||||
getKey,
|
||||
navigate
|
||||
} = new SeleniumHelper();
|
||||
|
||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
|
@ -21,7 +22,7 @@ describe('www-integration search', () => {
|
|||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(rootUrl);
|
||||
await navigate(rootUrl);
|
||||
});
|
||||
|
||||
afterAll(() => driver.quit());
|
||||
|
|
|
@ -8,9 +8,9 @@ const chromedriverVersion = require('chromedriver').version;
|
|||
|
||||
const headless = process.env.SMOKE_HEADLESS || false;
|
||||
const remote = process.env.SMOKE_REMOTE || false;
|
||||
const ci = process.env.CI || false;
|
||||
const usingCircle = process.env.CIRCLECI || false;
|
||||
const buildID = process.env.CIRCLE_BUILD_NUM || '0000';
|
||||
const ciBuildPrefix = process.env.CI ?
|
||||
`CI #${process.env.GITHUB_RUN_ID}/${process.env.GITHUB_RUN_ATTEMPT}` :
|
||||
''; // no prefix if not in CI
|
||||
const {SAUCE_USERNAME, SAUCE_ACCESS_KEY} = process.env;
|
||||
const {By, Key, until} = webdriver;
|
||||
|
||||
|
@ -135,8 +135,11 @@ class SeleniumHelper {
|
|||
'getDriver',
|
||||
'getLogs',
|
||||
'getSauceDriver',
|
||||
'isSignedIn',
|
||||
'navigate',
|
||||
'signIn',
|
||||
'urlMatches',
|
||||
'waitUntilDocumentReady',
|
||||
'waitUntilGone'
|
||||
]);
|
||||
|
||||
|
@ -157,9 +160,8 @@ class SeleniumHelper {
|
|||
buildDriver (name) {
|
||||
if (remote === 'true'){
|
||||
let nameToUse;
|
||||
if (ci === 'true'){
|
||||
const ciName = usingCircle ? 'circleCi ' : 'unknown ';
|
||||
nameToUse = `${ciName + buildID} : ${name}`;
|
||||
if (ciBuildPrefix){
|
||||
nameToUse = `${ciBuildPrefix}: ${name}`;
|
||||
} else {
|
||||
nameToUse = name;
|
||||
}
|
||||
|
@ -230,8 +232,7 @@ class SeleniumHelper {
|
|||
accessKey: accessKey,
|
||||
name: name
|
||||
})
|
||||
.usingServer(`http://${username}:${accessKey
|
||||
}@ondemand.saucelabs.com:80/wd/hub`)
|
||||
.usingServer(`http://${username}:${accessKey}@ondemand.saucelabs.com:80/wd/hub`)
|
||||
.build();
|
||||
return driver;
|
||||
}
|
||||
|
@ -247,6 +248,40 @@ class SeleniumHelper {
|
|||
return Key[keyName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until the document is ready (i.e. the document.readyState is 'complete')
|
||||
* @returns {Promise} A promise that resolves when the document is ready
|
||||
*/
|
||||
async waitUntilDocumentReady () {
|
||||
const outerError = new SeleniumHelperError('waitUntilDocumentReady failed');
|
||||
try {
|
||||
await this.driver.wait(
|
||||
async () => await this.driver.executeScript('return document.readyState;') === 'complete',
|
||||
DEFAULT_TIMEOUT_MILLISECONDS
|
||||
);
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the given URL and wait until the document is ready.
|
||||
* The Selenium docs say the promise returned by `driver.get()` "will be resolved when the document has finished
|
||||
* loading." In practice, that doesn't mean the page is ready for testing. I suspect it comes down to the
|
||||
* difference between "interactive" and "complete" (or `DOMContentLoaded` and `load`).
|
||||
* @param {string} url The URL to navigate to.
|
||||
* @returns {Promise} A promise that resolves when the document is ready
|
||||
*/
|
||||
async navigate (url) {
|
||||
const outerError = new SeleniumHelperError('navigate failed', [{url}]);
|
||||
try {
|
||||
await this.driver.get(url);
|
||||
await this.waitUntilDocumentReady();
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an element by xpath.
|
||||
* @param {string} xpath The xpath to search for.
|
||||
|
@ -284,8 +319,27 @@ class SeleniumHelper {
|
|||
async clickXpath (xpath) {
|
||||
const outerError = new SeleniumHelperError('clickXpath failed', [{xpath}]);
|
||||
try {
|
||||
const el = await this.findByXpath(xpath);
|
||||
await el.click();
|
||||
return await this.driver.wait(new webdriver.WebElementCondition(
|
||||
'for element click to succeed',
|
||||
async () => {
|
||||
const element = await this.findByXpath(xpath);
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
await element.click();
|
||||
return element;
|
||||
} catch (e) {
|
||||
if (e instanceof webdriver.error.ElementClickInterceptedError) {
|
||||
// something is in front of the element we want to click
|
||||
// probably the loading screen
|
||||
// this is the main reason for using wait()
|
||||
return null;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
), DEFAULT_TIMEOUT_MILLISECONDS);
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
|
@ -384,6 +438,57 @@ class SeleniumHelper {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} The xpath to the login button, which is present only if signed out.
|
||||
*/
|
||||
getPathForLogin () {
|
||||
return '//li[@class="link right login-item"]/a';
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} The xpath to the profile name, which is present only if signed in.
|
||||
*/
|
||||
getPathForProfileName () {
|
||||
return '//span[contains(@class, "profile-name")]';
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<boolean>} True if the user is signed in, false otherwise.
|
||||
* @throws {SeleniumHelperError} If the user's sign-in state cannot be determined.
|
||||
*/
|
||||
async isSignedIn () {
|
||||
const outerError = new SeleniumHelperError('isSignedIn failed');
|
||||
try {
|
||||
const state = await this.driver.wait(
|
||||
() => this.driver.executeScript(
|
||||
`
|
||||
if (document.evaluate(arguments[0], document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null)
|
||||
.singleNodeValue) {
|
||||
return 'signed in';
|
||||
}
|
||||
if (document.evaluate(arguments[1], document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null)
|
||||
.singleNodeValue) {
|
||||
return 'signed out';
|
||||
}
|
||||
`,
|
||||
this.getPathForProfileName(),
|
||||
this.getPathForLogin()
|
||||
),
|
||||
DEFAULT_TIMEOUT_MILLISECONDS
|
||||
);
|
||||
switch (state) {
|
||||
case 'signed in':
|
||||
return true;
|
||||
case 'signed out':
|
||||
return false;
|
||||
default:
|
||||
throw new Error('Could not determine whether or not user is signed in');
|
||||
}
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign in on a `scratch-www` page.
|
||||
* @param {string} username The username to sign in with.
|
||||
|
@ -396,12 +501,13 @@ class SeleniumHelper {
|
|||
{password: password ? 'provided' : 'absent'}
|
||||
]);
|
||||
try {
|
||||
await this.clickXpath('//li[@class="link right login-item"]/a');
|
||||
await this.clickXpath(this.getPathForLogin());
|
||||
const name = await this.findByXpath('//input[@id="frc-username-1088"]');
|
||||
await name.sendKeys(username);
|
||||
const word = await this.findByXpath('//input[@id="frc-password-1088"]');
|
||||
await word.sendKeys(password + this.getKey('ENTER'));
|
||||
await this.findByXpath('//span[contains(@class, "profile-name")]');
|
||||
await this.waitUntilDocumentReady();
|
||||
await this.findByXpath(this.getPathForProfileName());
|
||||
} catch (cause) {
|
||||
throw await outerError.chain(cause, this.driver);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ const {
|
|||
clickXpath,
|
||||
findByXpath,
|
||||
getKey,
|
||||
navigate,
|
||||
signIn,
|
||||
waitUntilVisible
|
||||
} = new SeleniumHelper();
|
||||
|
@ -34,13 +35,13 @@ describe('www-integration sign-in-and-out', () => {
|
|||
|
||||
describe('sign in', () => {
|
||||
afterEach(async () => {
|
||||
await driver.get(wwwURL);
|
||||
await navigate(wwwURL);
|
||||
await clickXpath('//div[@class="account-nav"]');
|
||||
await clickText('Sign out');
|
||||
});
|
||||
|
||||
test('sign in on www', async () => {
|
||||
await driver.get(wwwURL);
|
||||
await navigate(wwwURL);
|
||||
await driver.sleep(1000);
|
||||
await clickXpath('//li[@class="link right login-item"]/a');
|
||||
const name = await findByXpath('//input[@id="frc-username-1088"]');
|
||||
|
@ -57,7 +58,7 @@ describe('www-integration sign-in-and-out', () => {
|
|||
});
|
||||
|
||||
test('sign in on scratchr2', async () => {
|
||||
await driver.get(scratchr2url);
|
||||
await navigate(scratchr2url);
|
||||
await clickXpath('//li[@class="sign-in dropdown"]/span');
|
||||
const name = await findByXpath('//input[@id="login_dropdown_username"]');
|
||||
await name.sendKeys(username);
|
||||
|
@ -72,7 +73,7 @@ describe('www-integration sign-in-and-out', () => {
|
|||
|
||||
describe('sign out', () => {
|
||||
beforeEach(async () => {
|
||||
await driver.get(wwwURL);
|
||||
await navigate(wwwURL);
|
||||
await signIn(username, password);
|
||||
await driver.sleep(500);
|
||||
});
|
||||
|
@ -86,7 +87,7 @@ describe('www-integration sign-in-and-out', () => {
|
|||
});
|
||||
|
||||
test('sign out on scratchr2', async () => {
|
||||
await driver.get(scratchr2url);
|
||||
await navigate(scratchr2url);
|
||||
await clickXpath('//span[@class="user-name dropdown-toggle"]');
|
||||
await clickXpath('//li[@id="logout"]');
|
||||
const element = await findByXpath('//li[@class="link right login-item"]/a/span');
|
||||
|
@ -101,7 +102,7 @@ describe('www-integration sign-in-and-out', () => {
|
|||
const nonsenseUsername = Math.random().toString(36)
|
||||
.replace(/[^a-z]+/g, '')
|
||||
.substr(0, 5);
|
||||
await driver.get(scratchr2url);
|
||||
await navigate(scratchr2url);
|
||||
await clickXpath('//li[@class="sign-in dropdown"]/span');
|
||||
const name = await findByXpath('//input[@id="login_dropdown_username"]');
|
||||
await name.sendKeys(nonsenseUsername + getKey('ENTER'));
|
||||
|
@ -117,7 +118,7 @@ describe('www-integration sign-in-and-out', () => {
|
|||
const nonsenseUsername = Math.random().toString(36)
|
||||
.replace(/[^a-z]+/g, '')
|
||||
.substr(0, 5);
|
||||
await driver.get(scratchr2url);
|
||||
await navigate(scratchr2url);
|
||||
await clickXpath('//li[@class="sign-in dropdown"]/span');
|
||||
const name = await findByXpath('//input[@id="login_dropdown_username"]');
|
||||
await name.sendKeys(nonsenseUsername);
|
||||
|
@ -135,7 +136,7 @@ describe('www-integration sign-in-and-out', () => {
|
|||
const nonsensePassword = Math.random().toString(36)
|
||||
.replace(/[^a-z]+/g, '')
|
||||
.substr(0, 5);
|
||||
await driver.get(scratchr2url);
|
||||
await navigate(scratchr2url);
|
||||
await clickXpath('//li[@class="sign-in dropdown"]/span');
|
||||
const name = await findByXpath('//input[@id="login_dropdown_username"]');
|
||||
await name.sendKeys(username);
|
||||
|
|
|
@ -6,7 +6,8 @@ const {
|
|||
buildDriver,
|
||||
clickText,
|
||||
containsClass,
|
||||
findByXpath
|
||||
findByXpath,
|
||||
navigate
|
||||
} = new SeleniumHelper();
|
||||
|
||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||
|
@ -22,7 +23,7 @@ describe('www-integration statistics page', () => {
|
|||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(statisticsPage);
|
||||
await navigate(statisticsPage);
|
||||
});
|
||||
|
||||
afterAll(() => driver.quit());
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
import SeleniumHelper from './selenium-helpers.js';
|
||||
|
||||
const {
|
||||
findByXpath,
|
||||
buildDriver,
|
||||
clickXpath,
|
||||
clickText,
|
||||
clickXpath,
|
||||
findByXpath,
|
||||
isSignedIn,
|
||||
navigate,
|
||||
signIn
|
||||
} = new SeleniumHelper();
|
||||
|
||||
|
@ -33,11 +35,10 @@ describe('studio page while signed out', () => {
|
|||
beforeAll(async () => {
|
||||
// expect(projectUrl).toBe(defined);
|
||||
driver = await buildDriver('www-integration studio-page signed out');
|
||||
await driver.get(rootUrl);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await driver.get(studioUrl);
|
||||
await navigate(studioUrl);
|
||||
const studioNav = await findByXpath('//div[@class="studio-tabs"]');
|
||||
await studioNav.isDisplayed();
|
||||
});
|
||||
|
@ -45,7 +46,7 @@ describe('studio page while signed out', () => {
|
|||
afterAll(() => driver.quit());
|
||||
|
||||
test('land on projects tab', async () => {
|
||||
await driver.get(studioUrl);
|
||||
await navigate(studioUrl);
|
||||
const projectGrid = await findByXpath('//div[@class="studio-projects-grid"]');
|
||||
const projectGridDisplayed = await projectGrid.isDisplayed();
|
||||
expect(projectGridDisplayed).toBe(true);
|
||||
|
@ -70,13 +71,13 @@ describe('studio management', () => {
|
|||
|
||||
beforeAll(async () => {
|
||||
driver = await buildDriver('www-integration studio management');
|
||||
await driver.get(rootUrl);
|
||||
await navigate(rootUrl);
|
||||
|
||||
// create a studio for tests
|
||||
await signIn(username2, password);
|
||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
await driver.get(rateLimitCheck);
|
||||
await driver.get(myStuffURL);
|
||||
await navigate(rateLimitCheck);
|
||||
await navigate(myStuffURL);
|
||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||
await findByXpath('//div[@class="studio-tabs"]');
|
||||
promoteStudioURL = await driver.getCurrentUrl();
|
||||
|
@ -84,10 +85,10 @@ describe('studio management', () => {
|
|||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
if (await isSignedIn()) {
|
||||
await clickXpath('//a[contains(@class, "user-info")]');
|
||||
await clickText('Sign out');
|
||||
await driver.get(curatorTab);
|
||||
await findByXpath('//div[@class="studio-tabs"]');
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(() => driver.quit());
|
||||
|
@ -95,7 +96,7 @@ describe('studio management', () => {
|
|||
test('invite a curator', async () => {
|
||||
// sign in as user2
|
||||
await signIn(username2, password);
|
||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
await navigate(curatorTab);
|
||||
|
||||
// invite user3 to curate
|
||||
const inviteBox = await findByXpath('//div[@class="studio-adder-row"]/input');
|
||||
|
@ -110,7 +111,7 @@ describe('studio management', () => {
|
|||
test('accept curator invite', async () => {
|
||||
// Sign in user3
|
||||
await signIn(username3, password);
|
||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
await navigate(curatorTab);
|
||||
|
||||
// accept the curator invite
|
||||
await clickXpath('//button[@class="studio-invitation-button button"]');
|
||||
|
@ -125,7 +126,7 @@ describe('studio management', () => {
|
|||
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
// for some reason the user isn't showing up without waiting and reloading the page
|
||||
await driver.sleep(2000);
|
||||
await driver.get(curatorTab);
|
||||
await navigate(curatorTab);
|
||||
|
||||
// promote user3
|
||||
const user3href = `/users/${username3}`;
|
||||
|
@ -148,7 +149,7 @@ describe('studio management', () => {
|
|||
await signIn(username2, password);
|
||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||
// for some reason the user isn't showing up without reloading the page
|
||||
await driver.get(curatorTab);
|
||||
await navigate(curatorTab);
|
||||
|
||||
// open kebab menu
|
||||
const user2href = `/users/${username2}`;
|
||||
|
@ -180,8 +181,8 @@ describe('studio management', () => {
|
|||
// click confirm
|
||||
// await clickXpath('//button[contains(@class, "confirm-transfer-button")]')
|
||||
await clickXpath('//span[contains(text(), "Confirm")]/..');
|
||||
// findByXpath checks for both presence and visibility
|
||||
const transferSuccess = await findByXpath('//div[contains(@class, "alert-success")]');
|
||||
const successVisible = await transferSuccess.isDisplayed();
|
||||
expect(successVisible).toBe(true);
|
||||
expect(transferSuccess).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue