Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 96 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ AccessibilityDevTools enables static accessibility linting directly inside Xcode
* 🛠 **Inline errors inside Xcode** with remediation guidance
* ⚡ **Runs during build** using the SPM command plugin

---
## Supported projects types
1. Projects created with Swift package manager
2. Projects created with XCode

---
## Authentication
1. Log in to your BrowserStack account or [sign up](https://www.browserstack.com/users/sign_in) if you don’t have an account.
2. Obtain your **Username** and **Access Key** from the [Account & Profile section](https://www.browserstack.com/accounts/profile/details) section on the dashboard.
![Account & Profile section](./resources/a31150e8-6beb-4541-bc2a-e1d9f03e431d.png "Account & Profile section")
![Account & Profile section](./resources/accounts.png "Account & Profile section")

3. Set the following environment variables using the **Username** and **Access Key** you obtained in step 2.
* `BROWSERSTACK_USERNAME`
Expand All @@ -38,79 +43,123 @@ AccessibilityDevTools enables static accessibility linting directly inside Xcode

---
## Installation
### SwiftPM Projects
For SwiftPM projects, you can use the SPM `command plugin` for Accessibility DevTools.
### 1. Projects created with XCode
> Note: XCode projects don’t have a Package.swift file. However, the script will manage this for you. If you prefer not to do this or face any issues, you can use our CLI for linting instead.

**Add plugin in your `Package.swift`**
#### Clone Script
Run the following command at the <span style="color:red">root of your repository</span>

Edit the `Project.swift` to include following code. Specifically, these two things to be added
Zsh
```zsh
curl -L -o browserstack-a11y-scan-spm.sh "https://raw.githubusercontent.com/browserstack/AccessibilityDevTools/refs/heads/main/scripts/zsh/spm.sh" && chmod 0775 browserstack-a11y-scan-spm.sh
```

* Add `AccessibilityDevTools` as a package under dependencies
Bash
```bash
curl -L -o browserstack-a11y-scan-spm.sh "https://raw.githubusercontent.com/browserstack/AccessibilityDevTools/refs/heads/main/scripts/bash/spm.sh" && chmod 0775 browserstack-a11y-scan-spm.sh
```

Fish
```fish
curl -L -o browserstack-a11y-scan-spm.sh "https://raw.githubusercontent.com/browserstack/AccessibilityDevTools/refs/heads/main/scripts/fish/spm.sh" && chmod 0775 browserstack-a11y-scan-spm.sh
```

#### Add a Build Phase
Repeat these steps for each target in your project

1. Select a target from the targets left sidebar and go to Build Phases tab
2. Click + to create a new build phase. Name the newly created build phase to a name such as **BrowserStack Accessibility Linter**.
![Build Phase](./resources/build-phase.png "Build Phase")
3. Drag this newly created build phase above **Compile Sources** step
4. Delete any existing code in the newly created build step and add the following code.
5. Add this script:
```
./browserstack-a11y-scan-spm.sh --include **/*.swift --non-strict
```
Xcode will now automatically run the accessibility scan during builds.

### 2. Projects created with Swift package manager
**Register plugin as dependency in your `Package.swift` file**

Edit the `Package.swift` to include following code. Specifically, add these two things:

* Add `AccessibilityDevTools` as a package under dependencies
* Add `a11y-scan` as a plugin under each target that you have in your project

```swift
let package = Package(
name: "MySPMProject",
dependencies: [
.package(url: "https://github.com/browserstack/AccessibilityDevTools.git", from: "1.0.0")
name: "MyProject",
// platforms, products, etc.
dependencies: [
// other dependencies
.package(
url: "https://github.com/browserstack/AccessibilityDevTools.git",
branch: "main"
),
],
targets: [
.executableTarget(
name: "MyApp",
dependencies: [],
plugins: [
.plugin(name: "a11y-scan", package: "AccessibilityDevTools")
]
.executableTarget(
name: "MyApp",
dependencies: [],
plugins: [
.plugin(
name: "a11y-scan",
package: "AccessibilityDevTools"
)
]
)
]
)
```
**Add a Build Phase to run the plugin**
1. Select first item (project root) in the left folder tree and go to Build Phases tab
![Build Phases](./resources/25519c9c-87a9-41af-b97b-23f875faf3b7.png "Build Phases")
2. Click + to create a new build phase. Name the newly created build phase to a name such as **BrowserStack Accessibility Linter**
3. Drag this newly created build phase above **Compile Sources** step
4. Delete any existing code in the newly created build step and add the following code.
5. Add this script:
```bash
/usr/bin/xcrun swift package scan --disable-sandbox --include **/*.swift
```

Xcode will now automatically run the accessibility scan during builds.
#### Clone Script
Run the following command in the <span style="color:red;">root of your repository</span>

Zsh
```zsh
curl -L -o browserstack-a11y-scan-spm.sh "https://raw.githubusercontent.com/browserstack/AccessibilityDevTools/refs/heads/main/scripts/zsh/spm.sh" && chmod 0775 browserstack-a11y-scan-spm.sh
```

### Non-SwiftPM Projects
For all non-SwiftPM projects (e.g. Xcode projects), you can use the **browserstack-cli**
Bash
```bash
curl -L -o browserstack-a11y-scan-spm.sh "https://raw.githubusercontent.com/browserstack/AccessibilityDevTools/refs/heads/main/scripts/bash/spm.sh" && chmod 0775 browserstack-a11y-scan-spm.sh
```

**Install CLI in the project repo**
1. Open terminal and navigate to the project folder.
2. Run the commands provided in the [documentation](https://www.browserstack.com/docs/accessibility-dev-tools/run-checks-cli#install-the-cli)
Fish
```fish
curl -L -o browserstack-a11y-scan-spm.sh "https://raw.githubusercontent.com/browserstack/AccessibilityDevTools/refs/heads/main/scripts/fish/spm.sh" && chmod 0775 browserstack-a11y-scan-spm.sh
```

**Disable Sandboxing**
1. In Xcode project, select first item (project root) in the left folder tree and go to Build Settings tab
2. Search for sandbox > Set user script sandboxing to “NO”
#### Add a Build Phase
Repeat these steps for each target in your project

**Add a Build Phase to run the plugin**
1. Select first item (project root) in the left folder tree and go to Build Phases tab
![Build Phases](./resources/25519c9c-87a9-41af-b97b-23f875faf3b7.png "Build Phases")
1. Select a target from the targets left sidebar and go to Build Phases tab
2. Click + to create a new build phase. Name the newly created build phase to a name such as **BrowserStack Accessibility Linter**
![Build Phase](./resources/build-phase.png "Build Phase")
3. Drag this newly created build phase above **Compile Sources** step
4. Delete any existing code in the newly created build step and add the following code.
4. Delete any existing code in the newly created build step and add the following code.
5. Add this script:
```bash
./browserstack-cli accessibility --include **/*.swift
```
./browserstack-a11y-scan-spm.sh --include **/*.swift --non-strict
```
Xcode will now automatically run the accessibility scan during builds.

---
## Running Accessibility Scans
Press Cmd + B to build the project. If there are no errors from the linter (and any other build steps you have), the build will succeed.
Press Cmd + B to build the project.
* If there are any Accessibility issues, then they will be show up in the Issue Navigator.
![Issue Navigator](./resources/issue-navigator.png "Issue Navigator")
* The errors will also show up in the respective files, on the lines where the issue has occurred. Click on the cross mark to see the full error.
![Issue Details](./resources/issue-details.png "Issue Details")
* If --non-strict flag is passed, the build will succeed even if there are Accessibility issues. If --non-strict flag is not passed, then the build will fail if there are Accessibility issues.

If issues are found:

* Inline red markers show errors in files. Click on the cross mark to see the full error.
![Diagnostics](./resources/bb7fbc3b-6d19-47e9-93c5-8c4b0ae124a6.png "Diagnostics")
* All issues appear in the **Issue Navigator**
![Issue Navigator](./resources/ff9e25c4-0d57-4423-ae0f-fa77b56d99a7.png "Issue Navigator")
---
## Register pre-commit hook
You can run accessibility checks automatically before each commit by running the following command.
```bash
./browserstack-a11y-scan-spm.sh register-pre-commit-hook
```
You can then edit the `.git/hooks/pre-commit` file to customise the registered pre-commit hook.

---
## Support
Expand Down
Binary file not shown.
Binary file not shown.
Binary file added resources/build-phase.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file added resources/issue-details.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/issue-navigator.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
102 changes: 102 additions & 0 deletions scripts/bash/cli.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#!/usr/bin/env bash -il

GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
SCRIPT_PATH=$(realpath --relative-to="$GIT_ROOT" "$0" 2>/dev/null || realpath "$0")
SUBCOMMAND="$1"
EXTRA_ARGS=$@
CACHE_ROOT="${HOME}/.cache/browserstack/devtools/cli/"
BINARY_ZIP_PATH="${CACHE_ROOT}/browserstack-cli.zip"
BINARY_PATH="${CACHE_ROOT}/browserstack-cli"

mkdir -p "$CACHE_ROOT"

get_os() {
local uname_out
uname_out="$(uname -s)"
case "${uname_out}" in
Linux*) os_type=linux;;
Darwin*) os_type=macos;;
*) os_type="UNKNOWN:${uname_out}"
esac
echo "${os_type}"
}

get_arch() {
local arch_out
arch_out="$(uname -m)"
case "${arch_out}" in
x86_64*) arch_type=x64;;
arm64*) arch_type=arm64;;
*) arch_type="UNKNOWN:${arch_out}"
esac
echo "${arch_type}"
}

OS=$(get_os)
ARCH=$(get_arch)

register_git_hook() {
local hook_name="pre-commit"
local hook_path="${GIT_ROOT}/.git/hooks/${hook_name}"

# Check if the hook file already exists
if [ -f "${hook_path}" ]; then
# Append the script execution if not already present
if ! grep -q "${SCRIPT_PATH}" "${hook_path}"; then
echo "" >> "${hook_path}"
echo "# Hook to run accessibility scan before commit" >> "${hook_path}"
echo "${SCRIPT_PATH}" >> "${hook_path}"
echo "if [ \$? -ne 0 ]; then" >> "${hook_path}"
echo " echo \"Accessibility scan failed. Commit aborted.\"" >> "${hook_path}"
echo " exit 1" >> "${hook_path}"
echo "fi" >> "${hook_path}"
fi
else
# Create a new hook file
cat > "${hook_path}" <<EOF
#!/bin/sh
# Hook to run accessibility scan before commit
"${SCRIPT_PATH}"
if [ \$? -ne 0 ]; then
echo "Accessibility scan failed. Commit aborted."
exit 1
fi
EOF
chmod +x "${hook_path}" # Make the hook executable
fi
}

a11y_scan() {
if [[ -z "$EXTRA_ARGS" ]]; then
EXTRA_ARGS="--include **/*.swift"
fi
env -i HOME="$HOME" \
XCODE_VERSION_ACTUAL="$XCODE_VERSION_ACTUAL"\
BROWSERSTACK_USERNAME="$BROWSERSTACK_USERNAME"\
BROWSERSTACK_ACCESS_KEY="$BROWSERSTACK_ACCESS_KEY"\
PATH="$PATH" \
$BINARY_PATH a11y $EXTRA_ARGS
}

script_self_update() {
local remote_url="https://raw.githubusercontent.com/browserstack/AccessibilityDevTools/refs/heads/main/scripts/bash/spm.sh"

updated_script=$(curl -R -z "$SCRIPT_PATH" "$remote_url")
if [[ $updated_script =~ ^#! ]]; then
echo "$updated_script" > "$SCRIPT_PATH"
fi
}

download_binary() {
curl -R -z "$BINARY_ZIP_PATH" -L "http://api.browserstack.com/sdk/v1/download_cli?os=${OS}&os_arch=${ARCH}" -o "$BINARY_ZIP_PATH"
bsdtar -xvf "$BINARY_ZIP_PATH" -O > "$BINARY_PATH" && chmod 0775 "$BINARY_PATH"
}

script_self_update
if [[ $SUBCOMMAND == "register-pre-commit-hook" ]]; then
register_git_hook
exit 0
fi

download_binary
a11y_scan
101 changes: 101 additions & 0 deletions scripts/bash/spm.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/env bash -il

[ -f "${PWD}/Package.swift" ]
PACKAGE_EXISTS="$?"
GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
SCRIPT_PATH=$(realpath --relative-to="$GIT_ROOT" "$0" 2>/dev/null || realpath "$0")
SUBCOMMAND="$1"
EXTRA_ARGS=$@

register_git_hook() {
local hook_name="pre-commit"
local hook_path="${GIT_ROOT}/.git/hooks/${hook_name}"

# Check if the hook file already exists
if [ -f "${hook_path}" ]; then
# Append the script execution if not already present
if ! grep -q "${SCRIPT_PATH}" "${hook_path}"; then
echo "" >> "${hook_path}"
echo "# Hook to run accessibility scan before commit" >> "${hook_path}"
echo "${SCRIPT_PATH}" >> "${hook_path}"
echo "if [ \$? -ne 0 ]; then" >> "${hook_path}"
echo " echo \"Accessibility scan failed. Commit aborted.\"" >> "${hook_path}"
echo " exit 1" >> "${hook_path}"
echo "fi" >> "${hook_path}"
fi
else
# Create a new hook file
cat > "${hook_path}" <<EOF
#!/bin/sh
# Hook to run accessibility scan before commit
"${SCRIPT_PATH}"
if [ \$? -ne 0 ]; then
echo "Accessibility scan failed. Commit aborted."
exit 1
fi
EOF
chmod +x "${hook_path}" # Make the hook executable
fi
}

a11y_scan() {
# Ensure Package.swift is removed on exit (acts like a finally block)
cleanup() {
if [ $PACKAGE_EXISTS -eq 0 ]; then
return
fi
rm -f -- "${PWD}/Package.swift" "${PWD}/Package.resolved"
}
trap cleanup EXIT

setup() {
if [ $PACKAGE_EXISTS -eq 0 ]; then
return
fi

cat > Package.swift <<EOF
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
name: "Dummy",
dependencies: [
.package(url: "https://github.com/browserstack/AccessibilityDevTools.git", branch: "main")
],
targets: []
)
EOF
}

setup
if [[ -z "$EXTRA_ARGS" ]]; then
EXTRA_ARGS="--include **/*.swift"
fi
env -i HOME="$HOME" \
XCODE_VERSION_ACTUAL="$XCODE_VERSION_ACTUAL"\
BROWSERSTACK_USERNAME="$BROWSERSTACK_USERNAME"\
BROWSERSTACK_ACCESS_KEY="$BROWSERSTACK_ACCESS_KEY"\
PATH="$PATH" \
swift package plugin \
--allow-writing-to-directory ~/.cache\
--allow-writing-to-package-directory\
--allow-network-connections 'all(ports: [])'\
scan $EXTRA_ARGS
}

script_self_update() {
local remote_url="https://raw.githubusercontent.com/browserstack/AccessibilityDevTools/refs/heads/main/scripts/bash/spm.sh"

updated_script=$(curl -R -z "$SCRIPT_PATH" "$remote_url")
if [[ $updated_script =~ ^#! ]]; then
echo "$updated_script" > "$SCRIPT_PATH"
fi
}

script_self_update
if [[ $SUBCOMMAND == "register-pre-commit-hook" ]]; then
register_git_hook
exit 0
fi

a11y_scan
Loading