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": "npm run test:lint && npm run build && npm run test:unit",
|
||||||
"test:lint": "eslint . --ext .js,.jsx,.json",
|
"test:lint": "eslint . --ext .js,.jsx,.json",
|
||||||
"test:lint:ci": "eslint . --ext .js,.jsx,.json --format junit -o ./test/results/lint-results.xml",
|
"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": "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": "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": "npm run test:unit:jest:unit && npm run test:unit:jest:localization",
|
||||||
"test:unit:jest:unit": "jest ./test/unit/ --reporters=default",
|
"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,
|
clickXpath,
|
||||||
containsClass,
|
containsClass,
|
||||||
findByXpath,
|
findByXpath,
|
||||||
|
navigate,
|
||||||
signIn
|
signIn
|
||||||
} = new SeleniumHelper();
|
} = new SeleniumHelper();
|
||||||
|
|
||||||
|
@ -29,13 +30,12 @@ const studioId = process.env.COMMENT_STUDIO_ID || 10005646;
|
||||||
const studioUrl = `${rootUrl}/studios/${studioId}/comments`;
|
const studioUrl = `${rootUrl}/studios/${studioId}/comments`;
|
||||||
|
|
||||||
// setup comments to leave
|
// 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 date = new Date();
|
||||||
const dateString = `Y:${date.getFullYear()} - M:${date.getMonth() + 1} - D:${date.getDate()} ` +
|
const dateString = date.toISOString();
|
||||||
`: ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
|
const projectComment = `${dateString} project`;
|
||||||
const buildNumber = process.env.CIRCLE_BUILD_NUM || dateString;
|
const profileComment = `${dateString} profile`;
|
||||||
const projectComment = `${buildNumber} project`;
|
const studioComment = `${dateString} studio`;
|
||||||
const profileComment = `${buildNumber} profile`;
|
|
||||||
const studioComment = `${buildNumber} studio`;
|
|
||||||
|
|
||||||
const projectReply = `${projectComment} reply`;
|
const projectReply = `${projectComment} reply`;
|
||||||
const profileReply = `${profileComment} reply`;
|
const profileReply = `${profileComment} reply`;
|
||||||
|
@ -48,7 +48,7 @@ let driver;
|
||||||
describe('comment tests', () => {
|
describe('comment tests', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
driver = await buildDriver('www-integration project comments');
|
driver = await buildDriver('www-integration project comments');
|
||||||
await driver.get(rootUrl);
|
await navigate(rootUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => driver.quit());
|
afterAll(() => driver.quit());
|
||||||
|
@ -60,13 +60,13 @@ describe('comment tests', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await driver.get(rootUrl);
|
await navigate(rootUrl);
|
||||||
await clickXpath('//a[contains(@class, "user-info")]');
|
await clickXpath('//a[contains(@class, "user-info")]');
|
||||||
await clickText('Sign out');
|
await clickText('Sign out');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('leave comment on project', async () => {
|
test('leave comment on project', async () => {
|
||||||
await driver.get(projectUrl);
|
await navigate(projectUrl);
|
||||||
|
|
||||||
// leave the comment
|
// leave the comment
|
||||||
const commentBox = await findByXpath('//textArea[@name="compose-comment"]');
|
const commentBox = await findByXpath('//textArea[@name="compose-comment"]');
|
||||||
|
@ -82,7 +82,7 @@ describe('comment tests', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('leave comment on a profile', async () => {
|
test('leave comment on a profile', async () => {
|
||||||
await driver.get(profileUrl);
|
await navigate(profileUrl);
|
||||||
|
|
||||||
// leave the comment
|
// leave the comment
|
||||||
const commentXpath = '//form[@id="main-post-form"]/div/textArea';
|
const commentXpath = '//form[@id="main-post-form"]/div/textArea';
|
||||||
|
@ -97,11 +97,11 @@ describe('comment tests', () => {
|
||||||
expect(commentVisible).toBe(true);
|
expect(commentVisible).toBe(true);
|
||||||
|
|
||||||
// return to homepage to sign out with www
|
// return to homepage to sign out with www
|
||||||
await driver.get(rootUrl);
|
await navigate(rootUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('leave comment on studio', async () => {
|
test('leave comment on studio', async () => {
|
||||||
await driver.get(studioUrl);
|
await navigate(studioUrl);
|
||||||
|
|
||||||
// leave the comment
|
// leave the comment
|
||||||
const commentBox = await findByXpath('//textArea[@name="compose-comment"]');
|
const commentBox = await findByXpath('//textArea[@name="compose-comment"]');
|
||||||
|
@ -138,7 +138,7 @@ describe('comment tests', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('project comment message visible', async () => {
|
test('project comment message visible', async () => {
|
||||||
await driver.get(`${rootUrl}/messages`);
|
await navigate(`${rootUrl}/messages`);
|
||||||
|
|
||||||
const projectMessageXpath = '//p[@class="emoji-text mod-comment" ' +
|
const projectMessageXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||||
`and contains(text(), "${projectComment}")]`;
|
`and contains(text(), "${projectComment}")]`;
|
||||||
|
@ -148,7 +148,7 @@ describe('comment tests', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('profile comment message visible', async () => {
|
test('profile comment message visible', async () => {
|
||||||
await driver.get(`${rootUrl}/messages`);
|
await navigate(`${rootUrl}/messages`);
|
||||||
|
|
||||||
const profileMessageXpath = '//p[@class="emoji-text mod-comment" ' +
|
const profileMessageXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||||
`and contains(text(), "${profileComment}")]`;
|
`and contains(text(), "${profileComment}")]`;
|
||||||
|
@ -163,7 +163,7 @@ describe('comment tests', () => {
|
||||||
const projectLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
const projectLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||||
`and contains(text(), "${projectComment}")]/../../../p[@class = "comment-message-info"]/span/a[2]`;
|
`and contains(text(), "${projectComment}")]/../../../p[@class = "comment-message-info"]/span/a[2]`;
|
||||||
|
|
||||||
await driver.get(`${rootUrl}/messages`);
|
await navigate(`${rootUrl}/messages`);
|
||||||
await clickXpath(projectLinkXpath);
|
await clickXpath(projectLinkXpath);
|
||||||
|
|
||||||
// find green flag overlay
|
// find green flag overlay
|
||||||
|
@ -175,7 +175,7 @@ describe('comment tests', () => {
|
||||||
const projectLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
const projectLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||||
`and contains(text(), "${projectComment}")]/../../../p[@class = "comment-message-info"]/span/a[2]`;
|
`and contains(text(), "${projectComment}")]/../../../p[@class = "comment-message-info"]/span/a[2]`;
|
||||||
|
|
||||||
await driver.get(`${rootUrl}/messages`);
|
await navigate(`${rootUrl}/messages`);
|
||||||
await clickXpath(projectLinkXpath);
|
await clickXpath(projectLinkXpath);
|
||||||
|
|
||||||
const commentXpath = `//span[contains(text(), "${projectComment}")]`;
|
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]`;
|
`and contains(text(), "${projectComment}")]/../../../p[@class = "comment-message-info"]/span/a[2]`;
|
||||||
const containerXpath = `//span[contains(text(), "${projectComment}")]/../../../..`;
|
const containerXpath = `//span[contains(text(), "${projectComment}")]/../../../..`;
|
||||||
|
|
||||||
await driver.get(`${rootUrl}/messages`);
|
await navigate(`${rootUrl}/messages`);
|
||||||
await clickXpath(projectLinkXpath);
|
await clickXpath(projectLinkXpath);
|
||||||
|
|
||||||
const commentContainer = await findByXpath(containerXpath);
|
const commentContainer = await findByXpath(containerXpath);
|
||||||
|
@ -201,7 +201,7 @@ describe('comment tests', () => {
|
||||||
const profileLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
const profileLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||||
`and contains(text(), "${profileComment}")]/../../../` +
|
`and contains(text(), "${profileComment}")]/../../../` +
|
||||||
'p[@class = "comment-message-info"]/span/a[2]';
|
'p[@class = "comment-message-info"]/span/a[2]';
|
||||||
await driver.get(`${rootUrl}/messages`);
|
await navigate(`${rootUrl}/messages`);
|
||||||
await clickXpath(profileLinkXpath);
|
await clickXpath(profileLinkXpath);
|
||||||
|
|
||||||
// find profile data
|
// find profile data
|
||||||
|
@ -218,7 +218,7 @@ describe('comment tests', () => {
|
||||||
const profileLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
const profileLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||||
`and contains(text(), "${profileComment}")]/../../../` +
|
`and contains(text(), "${profileComment}")]/../../../` +
|
||||||
'p[@class = "comment-message-info"]/span/a[2]';
|
'p[@class = "comment-message-info"]/span/a[2]';
|
||||||
await driver.get(`${rootUrl}/messages`);
|
await navigate(`${rootUrl}/messages`);
|
||||||
await clickXpath(profileLinkXpath);
|
await clickXpath(profileLinkXpath);
|
||||||
|
|
||||||
// find comment
|
// find comment
|
||||||
|
@ -233,7 +233,7 @@ describe('comment tests', () => {
|
||||||
const profileLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
const profileLinkXpath = '//p[@class="emoji-text mod-comment" ' +
|
||||||
`and contains(text(), "${profileComment}")]/../../../` +
|
`and contains(text(), "${profileComment}")]/../../../` +
|
||||||
'p[@class = "comment-message-info"]/span/a[2]';
|
'p[@class = "comment-message-info"]/span/a[2]';
|
||||||
await driver.get(`${rootUrl}/messages`);
|
await navigate(`${rootUrl}/messages`);
|
||||||
await clickXpath(profileLinkXpath);
|
await clickXpath(profileLinkXpath);
|
||||||
|
|
||||||
// comment highlighted?
|
// comment highlighted?
|
||||||
|
@ -244,7 +244,7 @@ describe('comment tests', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('project: reply to comment', async () => {
|
test('project: reply to comment', async () => {
|
||||||
await driver.get(projectUrl);
|
await navigate(projectUrl);
|
||||||
const commentXpath = `//span[contains(text(), "${projectComment}")]/../..`;
|
const commentXpath = `//span[contains(text(), "${projectComment}")]/../..`;
|
||||||
const replyXpath = `${commentXpath}//span[@class = "comment-reply"]`;
|
const replyXpath = `${commentXpath}//span[@class = "comment-reply"]`;
|
||||||
await clickXpath(replyXpath);
|
await clickXpath(replyXpath);
|
||||||
|
@ -256,8 +256,7 @@ describe('comment tests', () => {
|
||||||
await composeBox.sendKeys(projectReply);
|
await composeBox.sendKeys(projectReply);
|
||||||
|
|
||||||
// click post
|
// click post
|
||||||
const postButton = await findByXpath(`${replyRow}//button[@class = "button compose-post"]`);
|
await clickXpath(`${replyRow}//button[@class = "button compose-post"]`);
|
||||||
await postButton.click();
|
|
||||||
|
|
||||||
const postedReply = await findByXpath(`//span[contains(text(), "${projectReply}")]`);
|
const postedReply = await findByXpath(`//span[contains(text(), "${projectReply}")]`);
|
||||||
const commentVisible = await postedReply.isDisplayed();
|
const commentVisible = await postedReply.isDisplayed();
|
||||||
|
@ -265,7 +264,7 @@ describe('comment tests', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('profile reply to comment', async () => {
|
test('profile reply to comment', async () => {
|
||||||
await driver.get(profileUrl);
|
await navigate(profileUrl);
|
||||||
// find the comment and click reply
|
// find the comment and click reply
|
||||||
const commentXpath = `//div[contains(text(), "${profileComment}")]/..`;
|
const commentXpath = `//div[contains(text(), "${profileComment}")]/..`;
|
||||||
await clickXpath(`${commentXpath}//a[@class = "reply"]`);
|
await clickXpath(`${commentXpath}//a[@class = "reply"]`);
|
||||||
|
@ -282,7 +281,7 @@ describe('comment tests', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('studio: reply to comment', async () => {
|
test('studio: reply to comment', async () => {
|
||||||
await driver.get(studioUrl);
|
await navigate(studioUrl);
|
||||||
|
|
||||||
// find the comment and click reply
|
// find the comment and click reply
|
||||||
const commentXpath = `//span[contains(text(), "${studioComment}")]/../..`;
|
const commentXpath = `//span[contains(text(), "${studioComment}")]/../..`;
|
||||||
|
|
|
@ -5,7 +5,9 @@ const SeleniumHelper = require('./selenium-helpers.js');
|
||||||
const {
|
const {
|
||||||
clickText,
|
clickText,
|
||||||
buildDriver,
|
buildDriver,
|
||||||
findText
|
findText,
|
||||||
|
navigate,
|
||||||
|
waitUntilDocumentReady
|
||||||
} = new SeleniumHelper();
|
} = new SeleniumHelper();
|
||||||
|
|
||||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||||
|
@ -20,7 +22,7 @@ describe('www-integration footer links', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await driver.get(rootUrl);
|
await navigate(rootUrl);
|
||||||
await findText('Create stories, games, and animations');
|
await findText('Create stories, games, and animations');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -30,6 +32,7 @@ describe('www-integration footer links', () => {
|
||||||
|
|
||||||
test('click About Scratch link', async () => {
|
test('click About Scratch link', async () => {
|
||||||
await clickText('About Scratch');
|
await clickText('About Scratch');
|
||||||
|
await waitUntilDocumentReady();
|
||||||
const url = await driver.getCurrentUrl();
|
const url = await driver.getCurrentUrl();
|
||||||
const pathname = (new URL(url)).pathname;
|
const pathname = (new URL(url)).pathname;
|
||||||
expect(pathname).toMatch(/^\/about\/?$/);
|
expect(pathname).toMatch(/^\/about\/?$/);
|
||||||
|
@ -37,6 +40,7 @@ describe('www-integration footer links', () => {
|
||||||
|
|
||||||
test('click For Parents link', async () => {
|
test('click For Parents link', async () => {
|
||||||
await clickText('For Parents');
|
await clickText('For Parents');
|
||||||
|
await waitUntilDocumentReady();
|
||||||
const url = await driver.getCurrentUrl();
|
const url = await driver.getCurrentUrl();
|
||||||
const pathname = (new URL(url)).pathname;
|
const pathname = (new URL(url)).pathname;
|
||||||
expect(pathname).toMatch(/^\/parents\/?$/);
|
expect(pathname).toMatch(/^\/parents\/?$/);
|
||||||
|
@ -44,6 +48,7 @@ describe('www-integration footer links', () => {
|
||||||
|
|
||||||
test('click For Educators link', async () => {
|
test('click For Educators link', async () => {
|
||||||
await clickText('For Educators');
|
await clickText('For Educators');
|
||||||
|
await waitUntilDocumentReady();
|
||||||
const url = await driver.getCurrentUrl();
|
const url = await driver.getCurrentUrl();
|
||||||
const pathname = (new URL(url)).pathname;
|
const pathname = (new URL(url)).pathname;
|
||||||
expect(pathname).toMatch(/^\/educators\/?$/);
|
expect(pathname).toMatch(/^\/educators\/?$/);
|
||||||
|
@ -51,6 +56,7 @@ describe('www-integration footer links', () => {
|
||||||
|
|
||||||
test('click For Developers link', async () => {
|
test('click For Developers link', async () => {
|
||||||
await clickText('For Developers');
|
await clickText('For Developers');
|
||||||
|
await waitUntilDocumentReady();
|
||||||
const url = await driver.getCurrentUrl();
|
const url = await driver.getCurrentUrl();
|
||||||
const pathname = (new URL(url)).pathname;
|
const pathname = (new URL(url)).pathname;
|
||||||
expect(pathname).toMatch(/^\/developers\/?$/);
|
expect(pathname).toMatch(/^\/developers\/?$/);
|
||||||
|
@ -60,6 +66,7 @@ describe('www-integration footer links', () => {
|
||||||
|
|
||||||
test('click Community Guidelines link', async () => {
|
test('click Community Guidelines link', async () => {
|
||||||
await clickText('Community Guidelines');
|
await clickText('Community Guidelines');
|
||||||
|
await waitUntilDocumentReady();
|
||||||
const url = await driver.getCurrentUrl();
|
const url = await driver.getCurrentUrl();
|
||||||
const pathname = (new URL(url)).pathname;
|
const pathname = (new URL(url)).pathname;
|
||||||
expect(pathname).toMatch(/^\/community_guidelines\/?$/);
|
expect(pathname).toMatch(/^\/community_guidelines\/?$/);
|
||||||
|
@ -67,6 +74,7 @@ describe('www-integration footer links', () => {
|
||||||
|
|
||||||
test('click Discussion Forums link', async () => {
|
test('click Discussion Forums link', async () => {
|
||||||
await clickText('Discussion Forums');
|
await clickText('Discussion Forums');
|
||||||
|
await waitUntilDocumentReady();
|
||||||
const url = await driver.getCurrentUrl();
|
const url = await driver.getCurrentUrl();
|
||||||
const pathname = (new URL(url)).pathname;
|
const pathname = (new URL(url)).pathname;
|
||||||
expect(pathname).toMatch(/^\/discuss\/?$/);
|
expect(pathname).toMatch(/^\/discuss\/?$/);
|
||||||
|
@ -74,6 +82,7 @@ describe('www-integration footer links', () => {
|
||||||
|
|
||||||
test('click Statistics link', async () => {
|
test('click Statistics link', async () => {
|
||||||
await clickText('Statistics');
|
await clickText('Statistics');
|
||||||
|
await waitUntilDocumentReady();
|
||||||
const url = await driver.getCurrentUrl();
|
const url = await driver.getCurrentUrl();
|
||||||
const pathname = (new URL(url)).pathname;
|
const pathname = (new URL(url)).pathname;
|
||||||
expect(pathname).toMatch(/^\/statistics\/?$/);
|
expect(pathname).toMatch(/^\/statistics\/?$/);
|
||||||
|
@ -83,6 +92,7 @@ describe('www-integration footer links', () => {
|
||||||
|
|
||||||
test('click Ideas link', async () => {
|
test('click Ideas link', async () => {
|
||||||
await clickText('Ideas');
|
await clickText('Ideas');
|
||||||
|
await waitUntilDocumentReady();
|
||||||
const url = await driver.getCurrentUrl();
|
const url = await driver.getCurrentUrl();
|
||||||
const pathname = (new URL(url)).pathname;
|
const pathname = (new URL(url)).pathname;
|
||||||
expect(pathname).toMatch(/^\/ideas\/?$/);
|
expect(pathname).toMatch(/^\/ideas\/?$/);
|
||||||
|
@ -90,6 +100,7 @@ describe('www-integration footer links', () => {
|
||||||
|
|
||||||
test('click FAQ link', async () => {
|
test('click FAQ link', async () => {
|
||||||
await clickText('FAQ');
|
await clickText('FAQ');
|
||||||
|
await waitUntilDocumentReady();
|
||||||
const url = await driver.getCurrentUrl();
|
const url = await driver.getCurrentUrl();
|
||||||
const pathname = (new URL(url)).pathname;
|
const pathname = (new URL(url)).pathname;
|
||||||
expect(pathname).toMatch(/^\/faq\/?$/);
|
expect(pathname).toMatch(/^\/faq\/?$/);
|
||||||
|
@ -97,6 +108,7 @@ describe('www-integration footer links', () => {
|
||||||
|
|
||||||
test('click Download link', async () => {
|
test('click Download link', async () => {
|
||||||
await clickText('Download');
|
await clickText('Download');
|
||||||
|
await waitUntilDocumentReady();
|
||||||
const url = await driver.getCurrentUrl();
|
const url = await driver.getCurrentUrl();
|
||||||
const pathname = (new URL(url)).pathname;
|
const pathname = (new URL(url)).pathname;
|
||||||
expect(pathname).toMatch(/^\/download\/?$/);
|
expect(pathname).toMatch(/^\/download\/?$/);
|
||||||
|
@ -104,6 +116,7 @@ describe('www-integration footer links', () => {
|
||||||
|
|
||||||
test('click Contact Us link', async () => {
|
test('click Contact Us link', async () => {
|
||||||
await clickText('Contact Us');
|
await clickText('Contact Us');
|
||||||
|
await waitUntilDocumentReady();
|
||||||
const url = await driver.getCurrentUrl();
|
const url = await driver.getCurrentUrl();
|
||||||
const pathname = (new URL(url)).pathname;
|
const pathname = (new URL(url)).pathname;
|
||||||
expect(pathname).toMatch(/^\/contact-us\/?$/);
|
expect(pathname).toMatch(/^\/contact-us\/?$/);
|
||||||
|
@ -113,6 +126,7 @@ describe('www-integration footer links', () => {
|
||||||
|
|
||||||
test('click Terms of Use link', async () => {
|
test('click Terms of Use link', async () => {
|
||||||
await clickText('Terms of Use');
|
await clickText('Terms of Use');
|
||||||
|
await waitUntilDocumentReady();
|
||||||
const url = await driver.getCurrentUrl();
|
const url = await driver.getCurrentUrl();
|
||||||
const pathname = (new URL(url)).pathname;
|
const pathname = (new URL(url)).pathname;
|
||||||
expect(pathname).toMatch(/^\/terms_of_use\/?$/);
|
expect(pathname).toMatch(/^\/terms_of_use\/?$/);
|
||||||
|
@ -120,6 +134,7 @@ describe('www-integration footer links', () => {
|
||||||
|
|
||||||
test('click Privacy Policy link', async () => {
|
test('click Privacy Policy link', async () => {
|
||||||
await clickText('Privacy Policy');
|
await clickText('Privacy Policy');
|
||||||
|
await waitUntilDocumentReady();
|
||||||
const url = await driver.getCurrentUrl();
|
const url = await driver.getCurrentUrl();
|
||||||
const pathname = (new URL(url)).pathname;
|
const pathname = (new URL(url)).pathname;
|
||||||
expect(pathname).toMatch(/^\/privacy_policy\/?$/);
|
expect(pathname).toMatch(/^\/privacy_policy\/?$/);
|
||||||
|
@ -127,6 +142,7 @@ describe('www-integration footer links', () => {
|
||||||
|
|
||||||
test('click Cookies link', async () => {
|
test('click Cookies link', async () => {
|
||||||
await clickText('Cookies');
|
await clickText('Cookies');
|
||||||
|
await waitUntilDocumentReady();
|
||||||
const url = await driver.getCurrentUrl();
|
const url = await driver.getCurrentUrl();
|
||||||
const pathname = (new URL(url)).pathname;
|
const pathname = (new URL(url)).pathname;
|
||||||
expect(pathname).toMatch(/^\/cookies\/?$/);
|
expect(pathname).toMatch(/^\/cookies\/?$/);
|
||||||
|
@ -139,23 +155,14 @@ describe('www-integration footer links', () => {
|
||||||
|
|
||||||
test('click DMCA link', async () => {
|
test('click DMCA link', async () => {
|
||||||
await clickText('DMCA');
|
await clickText('DMCA');
|
||||||
|
await waitUntilDocumentReady();
|
||||||
const url = await driver.getCurrentUrl();
|
const url = await driver.getCurrentUrl();
|
||||||
const pathname = (new URL(url)).pathname;
|
const pathname = (new URL(url)).pathname;
|
||||||
expect(pathname).toMatch(/^\/DMCA\/?$/);
|
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 the footer are skipped because they are not part of scratch-www
|
||||||
|
|
||||||
// The following links in are skipped because they are not on scratch.mit.edu
|
|
||||||
|
|
||||||
// Jobs
|
// Jobs
|
||||||
// Press
|
// Press
|
||||||
|
@ -166,3 +173,4 @@ describe('www-integration footer links', () => {
|
||||||
// SCRATCH JR (SCRATCHJR)
|
// SCRATCH JR (SCRATCHJR)
|
||||||
// SCRATCH DAY
|
// SCRATCH DAY
|
||||||
// SCRATCH FOUNDATION
|
// SCRATCH FOUNDATION
|
||||||
|
// Scratch Conference
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
const SeleniumHelper = require('./selenium-helpers.js');
|
const SeleniumHelper = require('./selenium-helpers.js');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
buildDriver,
|
||||||
clickXpath,
|
clickXpath,
|
||||||
findByXpath,
|
findByXpath,
|
||||||
buildDriver
|
navigate,
|
||||||
|
waitUntilDocumentReady
|
||||||
} = new SeleniumHelper();
|
} = new SeleniumHelper();
|
||||||
|
|
||||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||||
|
@ -17,11 +19,11 @@ let driver;
|
||||||
describe('www-integration project rows', () => {
|
describe('www-integration project rows', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
driver = await buildDriver('www-integration project rows');
|
driver = await buildDriver('www-integration project rows');
|
||||||
// driver.get(rootUrl);
|
// navigate(rootUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await driver.get(rootUrl);
|
await navigate(rootUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => driver.quit());
|
afterAll(() => driver.quit());
|
||||||
|
@ -49,6 +51,7 @@ describe('www-integration project rows', () => {
|
||||||
test('Featured Studios link', async () => {
|
test('Featured Studios link', async () => {
|
||||||
await clickXpath('//div[@class="box"][descendant::text()="Featured Studios"]' +
|
await clickXpath('//div[@class="box"][descendant::text()="Featured Studios"]' +
|
||||||
'//div[contains(@class, "thumbnail")][1]/a[@class="thumbnail-image"]');
|
'//div[contains(@class, "thumbnail")][1]/a[@class="thumbnail-image"]');
|
||||||
|
await waitUntilDocumentReady();
|
||||||
const studioInfo = await findByXpath('//div[contains(@class, "studio-info")]');
|
const studioInfo = await findByXpath('//div[contains(@class, "studio-info")]');
|
||||||
const studioInfoDisplayed = await studioInfo.isDisplayed();
|
const studioInfoDisplayed = await studioInfo.isDisplayed();
|
||||||
expect(studioInfoDisplayed).toBe(true);
|
expect(studioInfoDisplayed).toBe(true);
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
const SeleniumHelper = require('./selenium-helpers.js');
|
const SeleniumHelper = require('./selenium-helpers.js');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
findByXpath,
|
buildDriver,
|
||||||
clickXpath,
|
clickXpath,
|
||||||
buildDriver
|
findByXpath,
|
||||||
|
navigate,
|
||||||
|
waitUntilDocumentReady
|
||||||
} = new SeleniumHelper();
|
} = new SeleniumHelper();
|
||||||
|
|
||||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||||
|
@ -18,14 +20,14 @@ let driver;
|
||||||
describe('www-integration join flow', () => {
|
describe('www-integration join flow', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
driver = await buildDriver('www-integration join flow');
|
driver = await buildDriver('www-integration join flow');
|
||||||
await driver.get(rootUrl);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => driver.quit());
|
afterAll(() => driver.quit());
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await driver.get(rootUrl);
|
await navigate(rootUrl); // navigate to home page
|
||||||
await clickXpath('//a[@class="registrationLink"]');
|
await clickXpath('//a[@class="registrationLink"]'); // navigate to join page
|
||||||
|
await waitUntilDocumentReady();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('click Join opens join modal', async () => {
|
test('click Join opens join modal', async () => {
|
||||||
|
@ -35,22 +37,24 @@ describe('www-integration join flow', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('username validation message appears', async () => {
|
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 message = await findByXpath('//div[contains(@class, "validation-message")]');
|
||||||
const messageText = await message.getText();
|
const messageText = await message.getText();
|
||||||
expect(messageText).toEqual('Don\'t use your real name');
|
expect(messageText).toEqual('Don\'t use your real name');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('password validation message appears', async () => {
|
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 message = await findByXpath('//div[contains(@class, "validation-message")]');
|
||||||
const messageText = await message.getText();
|
const messageText = await message.getText();
|
||||||
expect(messageText).toContain('Write it down so you remember.');
|
expect(messageText).toContain('Write it down so you remember.');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('password confirmation validation message appears', async () => {
|
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 message = await findByXpath('//div[contains(@class, "validation-message")]');
|
||||||
const messageText = await message.getText();
|
const messageText = await message.getText();
|
||||||
expect(messageText).toEqual('Type password again');
|
expect(messageText).toEqual('Type password again');
|
||||||
|
@ -59,6 +63,7 @@ describe('www-integration join flow', () => {
|
||||||
test('username validation: too short', async () => {
|
test('username validation: too short', async () => {
|
||||||
const textInput = await findByXpath('//input[contains(@name, "username")]');
|
const textInput = await findByXpath('//input[contains(@name, "username")]');
|
||||||
await textInput.click();
|
await textInput.click();
|
||||||
|
await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', textInput));
|
||||||
await textInput.sendKeys('ab');
|
await textInput.sendKeys('ab');
|
||||||
await clickXpath('//div[@class = "join-flow-outer-content"]');
|
await clickXpath('//div[@class = "join-flow-outer-content"]');
|
||||||
const message = await findByXpath('//div[contains(@class, "validation-error")]');
|
const message = await findByXpath('//div[contains(@class, "validation-error")]');
|
||||||
|
@ -69,6 +74,7 @@ describe('www-integration join flow', () => {
|
||||||
test('username validation: username taken', async () => {
|
test('username validation: username taken', async () => {
|
||||||
const textInput = await findByXpath('//input[contains(@name, "username")]');
|
const textInput = await findByXpath('//input[contains(@name, "username")]');
|
||||||
await textInput.click();
|
await textInput.click();
|
||||||
|
await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', textInput));
|
||||||
await textInput.sendKeys(takenUsername);
|
await textInput.sendKeys(takenUsername);
|
||||||
await clickXpath('//div[@class = "join-flow-outer-content"]');
|
await clickXpath('//div[@class = "join-flow-outer-content"]');
|
||||||
const message = await findByXpath('//div[contains(@class, "validation-error")]');
|
const message = await findByXpath('//div[contains(@class, "validation-error")]');
|
||||||
|
@ -79,6 +85,7 @@ describe('www-integration join flow', () => {
|
||||||
test('username validation: bad word', async () => {
|
test('username validation: bad word', async () => {
|
||||||
const textInput = await findByXpath('//input[contains(@name, "username")]');
|
const textInput = await findByXpath('//input[contains(@name, "username")]');
|
||||||
await textInput.click();
|
await textInput.click();
|
||||||
|
await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', textInput));
|
||||||
// Should be caught by the filter
|
// Should be caught by the filter
|
||||||
await textInput.sendKeys('xxxxxxxxx');
|
await textInput.sendKeys('xxxxxxxxx');
|
||||||
await clickXpath('//div[@class = "join-flow-outer-content"]');
|
await clickXpath('//div[@class = "join-flow-outer-content"]');
|
||||||
|
|
|
@ -7,7 +7,10 @@ const {
|
||||||
clickText,
|
clickText,
|
||||||
clickXpath,
|
clickXpath,
|
||||||
findByXpath,
|
findByXpath,
|
||||||
signIn
|
navigate,
|
||||||
|
signIn,
|
||||||
|
urlMatches,
|
||||||
|
waitUntilDocumentReady
|
||||||
} = new SeleniumHelper();
|
} = new SeleniumHelper();
|
||||||
|
|
||||||
const username = `${process.env.SMOKE_USERNAME}1`;
|
const username = `${process.env.SMOKE_USERNAME}1`;
|
||||||
|
@ -24,8 +27,7 @@ let driver;
|
||||||
describe('www-integration my_stuff', () => {
|
describe('www-integration my_stuff', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
driver = await buildDriver('www-integration my_stuff');
|
driver = await buildDriver('www-integration my_stuff');
|
||||||
await driver.get(rootUrl);
|
await navigate(rootUrl);
|
||||||
await driver.sleep(1000);
|
|
||||||
await signIn(username, password);
|
await signIn(username, password);
|
||||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||||
});
|
});
|
||||||
|
@ -33,7 +35,7 @@ describe('www-integration my_stuff', () => {
|
||||||
afterAll(() => driver.quit());
|
afterAll(() => driver.quit());
|
||||||
|
|
||||||
test('verify My Stuff structure (tabs, title)', async () => {
|
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 header = await findByXpath('//div[@class="box-head"]/h2');
|
||||||
const headerVisible = await header.isDisplayed();
|
const headerVisible = await header.isDisplayed();
|
||||||
expect(headerVisible).toBe(true);
|
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 () => {
|
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 clickXpath('//span[@class="media-info-item title"]');
|
||||||
await driver.sleep(6000);
|
await waitUntilDocumentReady();
|
||||||
const gui = await findByXpath('//div[@class="guiPlayer"]');
|
const gui = await findByXpath('//div[@class="guiPlayer"]');
|
||||||
const guiVisible = await gui.isDisplayed();
|
const guiVisible = await gui.isDisplayed();
|
||||||
expect(guiVisible).toBe(true);
|
expect(guiVisible).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('clicking "see inside" should take you to the editor', async () => {
|
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 clickXpath('//a[@data-control="edit"]');
|
||||||
|
await waitUntilDocumentReady();
|
||||||
const gf = await findByXpath('//img[@class="green-flag_green-flag_1kiAo"]');
|
const gf = await findByXpath('//img[@class="green-flag_green-flag_1kiAo"]');
|
||||||
const gfVisible = await gf.isDisplayed();
|
const gfVisible = await gf.isDisplayed();
|
||||||
expect(gfVisible).toBe(true);
|
expect(gfVisible).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Add To button should bring up a list of studios', async () => {
|
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[@id="sidebar"]/ul/li[@data-tab="shared"]');
|
||||||
await clickXpath('//div[@data-control="add-to"]');
|
await clickXpath('//div[@data-control="add-to"]');
|
||||||
const dropDown = await findByXpath('//div[@class="dropdown-menu"]/ul/li');
|
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 () => {
|
test('+ New Project button should open the editor', async () => {
|
||||||
await driver.get(myStuffURL);
|
await navigate(myStuffURL);
|
||||||
await clickText('+ New Project');
|
await clickText('+ New Project');
|
||||||
|
await waitUntilDocumentReady();
|
||||||
const gf = await findByXpath('//img[@class="green-flag_green-flag_1kiAo"]');
|
const gf = await findByXpath('//img[@class="green-flag_green-flag_1kiAo"]');
|
||||||
const gfVisible = await gf.isDisplayed();
|
const gfVisible = await gf.isDisplayed();
|
||||||
expect(gfVisible).toBe(true);
|
expect(gfVisible).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('+ New Studio button should take you to the studio page', async () => {
|
test('+ New Studio button should take you to the studio page', async () => {
|
||||||
await driver.get(rateLimitCheck);
|
await navigate(rateLimitCheck);
|
||||||
await driver.get(myStuffURL);
|
await navigate(myStuffURL);
|
||||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||||
|
await waitUntilDocumentReady();
|
||||||
const tabs = await findByXpath('//div[@class="studio-tabs"]');
|
const tabs = await findByXpath('//div[@class="studio-tabs"]');
|
||||||
const tabsVisible = await tabs.isDisplayed();
|
const tabsVisible = await tabs.isDisplayed();
|
||||||
expect(tabsVisible).toBe(true);
|
expect(tabsVisible).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('New studio rate limited to five', async () => {
|
test('New studio rate limited to five', async () => {
|
||||||
await driver.get(rateLimitCheck);
|
await navigate(rateLimitCheck);
|
||||||
// 1st studio
|
// 1st studio
|
||||||
await driver.get(myStuffURL);
|
await navigate(myStuffURL);
|
||||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||||
await findByXpath('//div[@class="studio-tabs"]');
|
await urlMatches(/\/studios\//);
|
||||||
// 2nd studio
|
// 2nd studio
|
||||||
await driver.get(myStuffURL);
|
await navigate(myStuffURL);
|
||||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||||
await findByXpath('//div[@class="studio-tabs"]');
|
await urlMatches(/\/studios\//);
|
||||||
// 3rd studio
|
// 3rd studio
|
||||||
await driver.get(myStuffURL);
|
await navigate(myStuffURL);
|
||||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||||
await findByXpath('//div[@class="studio-tabs"]');
|
await urlMatches(/\/studios\//);
|
||||||
// 4th studio
|
// 4th studio
|
||||||
await driver.get(myStuffURL);
|
await navigate(myStuffURL);
|
||||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||||
await findByXpath('//div[@class="studio-tabs"]');
|
await urlMatches(/\/studios\//);
|
||||||
// 5th studio
|
// 5th studio
|
||||||
await driver.get(myStuffURL);
|
await navigate(myStuffURL);
|
||||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||||
await findByXpath('//div[@class="studio-tabs"]');
|
await urlMatches(/\/studios\//);
|
||||||
// 6th studio should fail
|
// 6th studio should fail
|
||||||
await driver.get(myStuffURL);
|
await navigate(myStuffURL);
|
||||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
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 alertMessage = await findByXpath('//div[contains(@class, "alert-error")]');
|
||||||
const errVisible = await alertMessage.isDisplayed();
|
expect(alertMessage).toBeTruthy();
|
||||||
expect(errVisible).toBe(true);
|
|
||||||
|
|
||||||
await driver.get(rateLimitCheck);
|
await navigate(rateLimitCheck);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,8 +4,9 @@ const SeleniumHelper = require('./selenium-helpers.js');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
clickXpath,
|
clickXpath,
|
||||||
|
buildDriver,
|
||||||
findByXpath,
|
findByXpath,
|
||||||
buildDriver
|
navigate
|
||||||
} = new SeleniumHelper();
|
} = new SeleniumHelper();
|
||||||
|
|
||||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||||
|
@ -20,7 +21,7 @@ describe('www-integration navbar links', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await driver.get(rootUrl);
|
await navigate(rootUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => driver.quit());
|
afterAll(() => driver.quit());
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// some tests use projects owned by user #2
|
// some tests use projects owned by user #2
|
||||||
|
|
||||||
const SeleniumHelper = require('./selenium-helpers.js');
|
const SeleniumHelper = require('./selenium-helpers.js');
|
||||||
|
const {until} = require('selenium-webdriver');
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -11,7 +12,9 @@ const {
|
||||||
clickXpath,
|
clickXpath,
|
||||||
findText,
|
findText,
|
||||||
findByXpath,
|
findByXpath,
|
||||||
|
isSignedIn,
|
||||||
signIn,
|
signIn,
|
||||||
|
navigate,
|
||||||
waitUntilVisible
|
waitUntilVisible
|
||||||
} = new SeleniumHelper();
|
} = new SeleniumHelper();
|
||||||
|
|
||||||
|
@ -52,11 +55,11 @@ describe('www-integration project-page signed out', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
// expect(projectUrl).toBe(defined);
|
// expect(projectUrl).toBe(defined);
|
||||||
driver = await buildDriver('www-integration project-page signed out');
|
driver = await buildDriver('www-integration project-page signed out');
|
||||||
await driver.get(rootUrl);
|
await navigate(rootUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await driver.get(unownedSharedUrl);
|
await navigate(unownedSharedUrl);
|
||||||
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||||
await waitUntilVisible(gfOverlay, driver);
|
await waitUntilVisible(gfOverlay, driver);
|
||||||
});
|
});
|
||||||
|
@ -103,7 +106,7 @@ describe('www-integration project-page signed out', () => {
|
||||||
|
|
||||||
// Load an unshared project while signed out, get error
|
// Load an unshared project while signed out, get error
|
||||||
test('Load an ushared project you do not own (error)', async () => {
|
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"]');
|
const unavailableImage = await findByXpath('//img[@class="not-available-image"]');
|
||||||
await waitUntilVisible(unavailableImage, driver);
|
await waitUntilVisible(unavailableImage, driver);
|
||||||
const unavailableVisible = await unavailableImage.isDisplayed();
|
const unavailableVisible = await unavailableImage.isDisplayed();
|
||||||
|
@ -117,14 +120,14 @@ describe('www-integration project-page signed in', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
// expect(projectUrl).toBe(defined);
|
// expect(projectUrl).toBe(defined);
|
||||||
driver = await buildDriver('www-integration project-page signed in');
|
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 () => {
|
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());
|
afterAll(() => driver.quit());
|
||||||
|
@ -133,7 +136,7 @@ describe('www-integration project-page signed in', () => {
|
||||||
|
|
||||||
// Load a shared project you own
|
// Load a shared project you own
|
||||||
test('Load a shared project you own', async () => {
|
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"]');
|
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||||
await waitUntilVisible(gfOverlay, driver);
|
await waitUntilVisible(gfOverlay, driver);
|
||||||
const gfVisible = await gfOverlay.isDisplayed();
|
const gfVisible = await gfOverlay.isDisplayed();
|
||||||
|
@ -142,7 +145,7 @@ describe('www-integration project-page signed in', () => {
|
||||||
|
|
||||||
// Load a shared project you don't own
|
// Load a shared project you don't own
|
||||||
test('Load a shared project you do not own', async () => {
|
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"]');
|
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||||
await waitUntilVisible(gfOverlay, driver);
|
await waitUntilVisible(gfOverlay, driver);
|
||||||
const gfVisible = await gfOverlay.isDisplayed();
|
const gfVisible = await gfOverlay.isDisplayed();
|
||||||
|
@ -151,7 +154,7 @@ describe('www-integration project-page signed in', () => {
|
||||||
|
|
||||||
// Load an unshared project you own
|
// Load an unshared project you own
|
||||||
test('Load an unshared project you own', async () => {
|
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"]');
|
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||||
await waitUntilVisible(gfOverlay, driver);
|
await waitUntilVisible(gfOverlay, driver);
|
||||||
const gfVisible = await gfOverlay.isDisplayed();
|
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
|
// Load an unshared project you don't own, get error
|
||||||
test('Load an ushared project you do not own (error)', async () => {
|
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"]');
|
const unavailableImage = await findByXpath('//img[@class="not-available-image"]');
|
||||||
await waitUntilVisible(unavailableImage, driver);
|
await waitUntilVisible(unavailableImage, driver);
|
||||||
const unavailableVisible = await unavailableImage.isDisplayed();
|
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
|
// Load a shared scratch 2 project you don't own
|
||||||
test('Load a shared scratch 2 project you do not own', async () => {
|
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"]');
|
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||||
await waitUntilVisible(gfOverlay, driver);
|
await waitUntilVisible(gfOverlay, driver);
|
||||||
const gfVisible = await gfOverlay.isDisplayed();
|
const gfVisible = await gfOverlay.isDisplayed();
|
||||||
|
@ -178,7 +181,7 @@ describe('www-integration project-page signed in', () => {
|
||||||
|
|
||||||
// Load an unshared scratch 2 project you own
|
// Load an unshared scratch 2 project you own
|
||||||
test('Load an unshared scratch 2 project you own', async () => {
|
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"]');
|
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||||
await waitUntilVisible(gfOverlay, driver);
|
await waitUntilVisible(gfOverlay, driver);
|
||||||
const gfVisible = await gfOverlay.isDisplayed();
|
const gfVisible = await gfOverlay.isDisplayed();
|
||||||
|
@ -188,32 +191,29 @@ describe('www-integration project-page signed in', () => {
|
||||||
|
|
||||||
describe('www-integration project-creation signed in', () => {
|
describe('www-integration project-creation signed in', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
// expect(projectUrl).toBe(defined);
|
|
||||||
driver = await buildDriver('www-integration project-creation signed in');
|
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
|
// 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
|
// https://support.saucelabs.com/hc/en-us/articles/115003685593-Uploading-Files-to-a-Sauce-Labs-Virtual-Machine-during-a-Test
|
||||||
if (remote) {
|
if (remote) {
|
||||||
await driver.get('https://github.com/scratchfoundation/scratch-www/blob/develop/test/fixtures/project1.sb3');
|
await navigate('https://github.com/scratchfoundation/scratch-www/blob/develop/test/fixtures/project1.sb3');
|
||||||
await clickXpath('//Button[@data-testid="download-raw-button"]');
|
await clickXpath('//button[@data-testid="download-raw-button"]');
|
||||||
await driver.sleep(3000);
|
await driver.sleep(3000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
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());
|
afterAll(() => driver.quit());
|
||||||
|
|
||||||
test('make a copy of a project', async () => {
|
test('make a copy of a project', async () => {
|
||||||
await driver.get(`${ownedUnsharedUrl}/editor`);
|
await navigate(`${ownedUnsharedUrl}/editor`);
|
||||||
const gf = await findByXpath('//img[@class="green-flag_green-flag_1kiAo"]');
|
|
||||||
await gf.isDisplayed();
|
|
||||||
await clickXpath(FILE_MENU_XPATH);
|
await clickXpath(FILE_MENU_XPATH);
|
||||||
await clickText('Save as a copy');
|
await clickText('Save as a copy');
|
||||||
const successAlert = await findText('Project saved 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 () => {
|
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"]');
|
const gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
|
||||||
await waitUntilVisible(gfOverlay, driver);
|
await waitUntilVisible(gfOverlay, driver);
|
||||||
await clickXpath('//button[@class="button remix-button"]');
|
await clickXpath('//button[@class="button remix-button"]');
|
||||||
|
@ -245,17 +245,17 @@ describe('www-integration project-creation signed in', () => {
|
||||||
'/Users/chef/Downloads/project1.sb3' :
|
'/Users/chef/Downloads/project1.sb3' :
|
||||||
path.resolve(__dirname, '../fixtures/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"]');
|
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 clickXpath(FILE_MENU_XPATH);
|
||||||
await clickText('Load from your computer');
|
await clickText('Load from your computer');
|
||||||
await driver.sleep(1000);
|
|
||||||
const input = await findByXpath('//input[@accept=".sb,.sb2,.sb3"]');
|
const input = await findByXpath('//input[@accept=".sb,.sb2,.sb3"]');
|
||||||
await input.sendKeys(projectPath);
|
await input.sendKeys(projectPath);
|
||||||
|
|
||||||
// accept alert
|
// accept alert
|
||||||
|
await driver.wait(until.alertIsPresent());
|
||||||
const alert = await driver.switchTo().alert();
|
const alert = await driver.switchTo().alert();
|
||||||
await alert.accept();
|
await alert.accept();
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,8 @@ const {
|
||||||
buildDriver,
|
buildDriver,
|
||||||
clickXpath,
|
clickXpath,
|
||||||
findByXpath,
|
findByXpath,
|
||||||
getKey
|
getKey,
|
||||||
|
navigate
|
||||||
} = new SeleniumHelper();
|
} = new SeleniumHelper();
|
||||||
|
|
||||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||||
|
@ -21,7 +22,7 @@ describe('www-integration search', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await driver.get(rootUrl);
|
await navigate(rootUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => driver.quit());
|
afterAll(() => driver.quit());
|
||||||
|
|
|
@ -8,9 +8,9 @@ const chromedriverVersion = require('chromedriver').version;
|
||||||
|
|
||||||
const headless = process.env.SMOKE_HEADLESS || false;
|
const headless = process.env.SMOKE_HEADLESS || false;
|
||||||
const remote = process.env.SMOKE_REMOTE || false;
|
const remote = process.env.SMOKE_REMOTE || false;
|
||||||
const ci = process.env.CI || false;
|
const ciBuildPrefix = process.env.CI ?
|
||||||
const usingCircle = process.env.CIRCLECI || false;
|
`CI #${process.env.GITHUB_RUN_ID}/${process.env.GITHUB_RUN_ATTEMPT}` :
|
||||||
const buildID = process.env.CIRCLE_BUILD_NUM || '0000';
|
''; // no prefix if not in CI
|
||||||
const {SAUCE_USERNAME, SAUCE_ACCESS_KEY} = process.env;
|
const {SAUCE_USERNAME, SAUCE_ACCESS_KEY} = process.env;
|
||||||
const {By, Key, until} = webdriver;
|
const {By, Key, until} = webdriver;
|
||||||
|
|
||||||
|
@ -135,8 +135,11 @@ class SeleniumHelper {
|
||||||
'getDriver',
|
'getDriver',
|
||||||
'getLogs',
|
'getLogs',
|
||||||
'getSauceDriver',
|
'getSauceDriver',
|
||||||
|
'isSignedIn',
|
||||||
|
'navigate',
|
||||||
'signIn',
|
'signIn',
|
||||||
'urlMatches',
|
'urlMatches',
|
||||||
|
'waitUntilDocumentReady',
|
||||||
'waitUntilGone'
|
'waitUntilGone'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -157,9 +160,8 @@ class SeleniumHelper {
|
||||||
buildDriver (name) {
|
buildDriver (name) {
|
||||||
if (remote === 'true'){
|
if (remote === 'true'){
|
||||||
let nameToUse;
|
let nameToUse;
|
||||||
if (ci === 'true'){
|
if (ciBuildPrefix){
|
||||||
const ciName = usingCircle ? 'circleCi ' : 'unknown ';
|
nameToUse = `${ciBuildPrefix}: ${name}`;
|
||||||
nameToUse = `${ciName + buildID} : ${name}`;
|
|
||||||
} else {
|
} else {
|
||||||
nameToUse = name;
|
nameToUse = name;
|
||||||
}
|
}
|
||||||
|
@ -230,8 +232,7 @@ class SeleniumHelper {
|
||||||
accessKey: accessKey,
|
accessKey: accessKey,
|
||||||
name: name
|
name: name
|
||||||
})
|
})
|
||||||
.usingServer(`http://${username}:${accessKey
|
.usingServer(`http://${username}:${accessKey}@ondemand.saucelabs.com:80/wd/hub`)
|
||||||
}@ondemand.saucelabs.com:80/wd/hub`)
|
|
||||||
.build();
|
.build();
|
||||||
return driver;
|
return driver;
|
||||||
}
|
}
|
||||||
|
@ -247,6 +248,40 @@ class SeleniumHelper {
|
||||||
return Key[keyName];
|
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.
|
* Find an element by xpath.
|
||||||
* @param {string} xpath The xpath to search for.
|
* @param {string} xpath The xpath to search for.
|
||||||
|
@ -284,8 +319,27 @@ class SeleniumHelper {
|
||||||
async clickXpath (xpath) {
|
async clickXpath (xpath) {
|
||||||
const outerError = new SeleniumHelperError('clickXpath failed', [{xpath}]);
|
const outerError = new SeleniumHelperError('clickXpath failed', [{xpath}]);
|
||||||
try {
|
try {
|
||||||
const el = await this.findByXpath(xpath);
|
return await this.driver.wait(new webdriver.WebElementCondition(
|
||||||
await el.click();
|
'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) {
|
} catch (cause) {
|
||||||
throw await outerError.chain(cause, this.driver);
|
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.
|
* Sign in on a `scratch-www` page.
|
||||||
* @param {string} username The username to sign in with.
|
* @param {string} username The username to sign in with.
|
||||||
|
@ -396,12 +501,13 @@ class SeleniumHelper {
|
||||||
{password: password ? 'provided' : 'absent'}
|
{password: password ? 'provided' : 'absent'}
|
||||||
]);
|
]);
|
||||||
try {
|
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"]');
|
const name = await this.findByXpath('//input[@id="frc-username-1088"]');
|
||||||
await name.sendKeys(username);
|
await name.sendKeys(username);
|
||||||
const word = await this.findByXpath('//input[@id="frc-password-1088"]');
|
const word = await this.findByXpath('//input[@id="frc-password-1088"]');
|
||||||
await word.sendKeys(password + this.getKey('ENTER'));
|
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) {
|
} catch (cause) {
|
||||||
throw await outerError.chain(cause, this.driver);
|
throw await outerError.chain(cause, this.driver);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ const {
|
||||||
clickXpath,
|
clickXpath,
|
||||||
findByXpath,
|
findByXpath,
|
||||||
getKey,
|
getKey,
|
||||||
|
navigate,
|
||||||
signIn,
|
signIn,
|
||||||
waitUntilVisible
|
waitUntilVisible
|
||||||
} = new SeleniumHelper();
|
} = new SeleniumHelper();
|
||||||
|
@ -34,13 +35,13 @@ describe('www-integration sign-in-and-out', () => {
|
||||||
|
|
||||||
describe('sign in', () => {
|
describe('sign in', () => {
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await driver.get(wwwURL);
|
await navigate(wwwURL);
|
||||||
await clickXpath('//div[@class="account-nav"]');
|
await clickXpath('//div[@class="account-nav"]');
|
||||||
await clickText('Sign out');
|
await clickText('Sign out');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('sign in on www', async () => {
|
test('sign in on www', async () => {
|
||||||
await driver.get(wwwURL);
|
await navigate(wwwURL);
|
||||||
await driver.sleep(1000);
|
await driver.sleep(1000);
|
||||||
await clickXpath('//li[@class="link right login-item"]/a');
|
await clickXpath('//li[@class="link right login-item"]/a');
|
||||||
const name = await findByXpath('//input[@id="frc-username-1088"]');
|
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 () => {
|
test('sign in on scratchr2', async () => {
|
||||||
await driver.get(scratchr2url);
|
await navigate(scratchr2url);
|
||||||
await clickXpath('//li[@class="sign-in dropdown"]/span');
|
await clickXpath('//li[@class="sign-in dropdown"]/span');
|
||||||
const name = await findByXpath('//input[@id="login_dropdown_username"]');
|
const name = await findByXpath('//input[@id="login_dropdown_username"]');
|
||||||
await name.sendKeys(username);
|
await name.sendKeys(username);
|
||||||
|
@ -72,7 +73,7 @@ describe('www-integration sign-in-and-out', () => {
|
||||||
|
|
||||||
describe('sign out', () => {
|
describe('sign out', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await driver.get(wwwURL);
|
await navigate(wwwURL);
|
||||||
await signIn(username, password);
|
await signIn(username, password);
|
||||||
await driver.sleep(500);
|
await driver.sleep(500);
|
||||||
});
|
});
|
||||||
|
@ -86,7 +87,7 @@ describe('www-integration sign-in-and-out', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('sign out on scratchr2', async () => {
|
test('sign out on scratchr2', async () => {
|
||||||
await driver.get(scratchr2url);
|
await navigate(scratchr2url);
|
||||||
await clickXpath('//span[@class="user-name dropdown-toggle"]');
|
await clickXpath('//span[@class="user-name dropdown-toggle"]');
|
||||||
await clickXpath('//li[@id="logout"]');
|
await clickXpath('//li[@id="logout"]');
|
||||||
const element = await findByXpath('//li[@class="link right login-item"]/a/span');
|
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)
|
const nonsenseUsername = Math.random().toString(36)
|
||||||
.replace(/[^a-z]+/g, '')
|
.replace(/[^a-z]+/g, '')
|
||||||
.substr(0, 5);
|
.substr(0, 5);
|
||||||
await driver.get(scratchr2url);
|
await navigate(scratchr2url);
|
||||||
await clickXpath('//li[@class="sign-in dropdown"]/span');
|
await clickXpath('//li[@class="sign-in dropdown"]/span');
|
||||||
const name = await findByXpath('//input[@id="login_dropdown_username"]');
|
const name = await findByXpath('//input[@id="login_dropdown_username"]');
|
||||||
await name.sendKeys(nonsenseUsername + getKey('ENTER'));
|
await name.sendKeys(nonsenseUsername + getKey('ENTER'));
|
||||||
|
@ -117,7 +118,7 @@ describe('www-integration sign-in-and-out', () => {
|
||||||
const nonsenseUsername = Math.random().toString(36)
|
const nonsenseUsername = Math.random().toString(36)
|
||||||
.replace(/[^a-z]+/g, '')
|
.replace(/[^a-z]+/g, '')
|
||||||
.substr(0, 5);
|
.substr(0, 5);
|
||||||
await driver.get(scratchr2url);
|
await navigate(scratchr2url);
|
||||||
await clickXpath('//li[@class="sign-in dropdown"]/span');
|
await clickXpath('//li[@class="sign-in dropdown"]/span');
|
||||||
const name = await findByXpath('//input[@id="login_dropdown_username"]');
|
const name = await findByXpath('//input[@id="login_dropdown_username"]');
|
||||||
await name.sendKeys(nonsenseUsername);
|
await name.sendKeys(nonsenseUsername);
|
||||||
|
@ -135,7 +136,7 @@ describe('www-integration sign-in-and-out', () => {
|
||||||
const nonsensePassword = Math.random().toString(36)
|
const nonsensePassword = Math.random().toString(36)
|
||||||
.replace(/[^a-z]+/g, '')
|
.replace(/[^a-z]+/g, '')
|
||||||
.substr(0, 5);
|
.substr(0, 5);
|
||||||
await driver.get(scratchr2url);
|
await navigate(scratchr2url);
|
||||||
await clickXpath('//li[@class="sign-in dropdown"]/span');
|
await clickXpath('//li[@class="sign-in dropdown"]/span');
|
||||||
const name = await findByXpath('//input[@id="login_dropdown_username"]');
|
const name = await findByXpath('//input[@id="login_dropdown_username"]');
|
||||||
await name.sendKeys(username);
|
await name.sendKeys(username);
|
||||||
|
|
|
@ -6,7 +6,8 @@ const {
|
||||||
buildDriver,
|
buildDriver,
|
||||||
clickText,
|
clickText,
|
||||||
containsClass,
|
containsClass,
|
||||||
findByXpath
|
findByXpath,
|
||||||
|
navigate
|
||||||
} = new SeleniumHelper();
|
} = new SeleniumHelper();
|
||||||
|
|
||||||
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
|
||||||
|
@ -22,7 +23,7 @@ describe('www-integration statistics page', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await driver.get(statisticsPage);
|
await navigate(statisticsPage);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => driver.quit());
|
afterAll(() => driver.quit());
|
||||||
|
|
|
@ -3,10 +3,12 @@
|
||||||
import SeleniumHelper from './selenium-helpers.js';
|
import SeleniumHelper from './selenium-helpers.js';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
findByXpath,
|
|
||||||
buildDriver,
|
buildDriver,
|
||||||
clickXpath,
|
|
||||||
clickText,
|
clickText,
|
||||||
|
clickXpath,
|
||||||
|
findByXpath,
|
||||||
|
isSignedIn,
|
||||||
|
navigate,
|
||||||
signIn
|
signIn
|
||||||
} = new SeleniumHelper();
|
} = new SeleniumHelper();
|
||||||
|
|
||||||
|
@ -33,11 +35,10 @@ describe('studio page while signed out', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
// expect(projectUrl).toBe(defined);
|
// expect(projectUrl).toBe(defined);
|
||||||
driver = await buildDriver('www-integration studio-page signed out');
|
driver = await buildDriver('www-integration studio-page signed out');
|
||||||
await driver.get(rootUrl);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await driver.get(studioUrl);
|
await navigate(studioUrl);
|
||||||
const studioNav = await findByXpath('//div[@class="studio-tabs"]');
|
const studioNav = await findByXpath('//div[@class="studio-tabs"]');
|
||||||
await studioNav.isDisplayed();
|
await studioNav.isDisplayed();
|
||||||
});
|
});
|
||||||
|
@ -45,7 +46,7 @@ describe('studio page while signed out', () => {
|
||||||
afterAll(() => driver.quit());
|
afterAll(() => driver.quit());
|
||||||
|
|
||||||
test('land on projects tab', async () => {
|
test('land on projects tab', async () => {
|
||||||
await driver.get(studioUrl);
|
await navigate(studioUrl);
|
||||||
const projectGrid = await findByXpath('//div[@class="studio-projects-grid"]');
|
const projectGrid = await findByXpath('//div[@class="studio-projects-grid"]');
|
||||||
const projectGridDisplayed = await projectGrid.isDisplayed();
|
const projectGridDisplayed = await projectGrid.isDisplayed();
|
||||||
expect(projectGridDisplayed).toBe(true);
|
expect(projectGridDisplayed).toBe(true);
|
||||||
|
@ -70,13 +71,13 @@ describe('studio management', () => {
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
driver = await buildDriver('www-integration studio management');
|
driver = await buildDriver('www-integration studio management');
|
||||||
await driver.get(rootUrl);
|
await navigate(rootUrl);
|
||||||
|
|
||||||
// create a studio for tests
|
// create a studio for tests
|
||||||
await signIn(username2, password);
|
await signIn(username2, password);
|
||||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||||
await driver.get(rateLimitCheck);
|
await navigate(rateLimitCheck);
|
||||||
await driver.get(myStuffURL);
|
await navigate(myStuffURL);
|
||||||
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
await clickXpath('//form[@id="new_studio"]/button[@type="submit"]');
|
||||||
await findByXpath('//div[@class="studio-tabs"]');
|
await findByXpath('//div[@class="studio-tabs"]');
|
||||||
promoteStudioURL = await driver.getCurrentUrl();
|
promoteStudioURL = await driver.getCurrentUrl();
|
||||||
|
@ -84,10 +85,10 @@ describe('studio management', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
if (await isSignedIn()) {
|
||||||
await clickXpath('//a[contains(@class, "user-info")]');
|
await clickXpath('//a[contains(@class, "user-info")]');
|
||||||
await clickText('Sign out');
|
await clickText('Sign out');
|
||||||
await driver.get(curatorTab);
|
}
|
||||||
await findByXpath('//div[@class="studio-tabs"]');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => driver.quit());
|
afterAll(() => driver.quit());
|
||||||
|
@ -95,7 +96,7 @@ describe('studio management', () => {
|
||||||
test('invite a curator', async () => {
|
test('invite a curator', async () => {
|
||||||
// sign in as user2
|
// sign in as user2
|
||||||
await signIn(username2, password);
|
await signIn(username2, password);
|
||||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
await navigate(curatorTab);
|
||||||
|
|
||||||
// invite user3 to curate
|
// invite user3 to curate
|
||||||
const inviteBox = await findByXpath('//div[@class="studio-adder-row"]/input');
|
const inviteBox = await findByXpath('//div[@class="studio-adder-row"]/input');
|
||||||
|
@ -110,7 +111,7 @@ describe('studio management', () => {
|
||||||
test('accept curator invite', async () => {
|
test('accept curator invite', async () => {
|
||||||
// Sign in user3
|
// Sign in user3
|
||||||
await signIn(username3, password);
|
await signIn(username3, password);
|
||||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
await navigate(curatorTab);
|
||||||
|
|
||||||
// accept the curator invite
|
// accept the curator invite
|
||||||
await clickXpath('//button[@class="studio-invitation-button button"]');
|
await clickXpath('//button[@class="studio-invitation-button button"]');
|
||||||
|
@ -125,7 +126,7 @@ describe('studio management', () => {
|
||||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||||
// for some reason the user isn't showing up without waiting and reloading the page
|
// for some reason the user isn't showing up without waiting and reloading the page
|
||||||
await driver.sleep(2000);
|
await driver.sleep(2000);
|
||||||
await driver.get(curatorTab);
|
await navigate(curatorTab);
|
||||||
|
|
||||||
// promote user3
|
// promote user3
|
||||||
const user3href = `/users/${username3}`;
|
const user3href = `/users/${username3}`;
|
||||||
|
@ -148,7 +149,7 @@ describe('studio management', () => {
|
||||||
await signIn(username2, password);
|
await signIn(username2, password);
|
||||||
await findByXpath('//span[contains(@class, "profile-name")]');
|
await findByXpath('//span[contains(@class, "profile-name")]');
|
||||||
// for some reason the user isn't showing up without reloading the page
|
// for some reason the user isn't showing up without reloading the page
|
||||||
await driver.get(curatorTab);
|
await navigate(curatorTab);
|
||||||
|
|
||||||
// open kebab menu
|
// open kebab menu
|
||||||
const user2href = `/users/${username2}`;
|
const user2href = `/users/${username2}`;
|
||||||
|
@ -180,8 +181,8 @@ describe('studio management', () => {
|
||||||
// click confirm
|
// click confirm
|
||||||
// await clickXpath('//button[contains(@class, "confirm-transfer-button")]')
|
// await clickXpath('//button[contains(@class, "confirm-transfer-button")]')
|
||||||
await clickXpath('//span[contains(text(), "Confirm")]/..');
|
await clickXpath('//span[contains(text(), "Confirm")]/..');
|
||||||
|
// findByXpath checks for both presence and visibility
|
||||||
const transferSuccess = await findByXpath('//div[contains(@class, "alert-success")]');
|
const transferSuccess = await findByXpath('//div[contains(@class, "alert-success")]');
|
||||||
const successVisible = await transferSuccess.isDisplayed();
|
expect(transferSuccess).toBeTruthy();
|
||||||
expect(successVisible).toBe(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue