Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(launcher): support Firefox as a snap #21328

Merged
merged 9 commits into from May 5, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
43 changes: 41 additions & 2 deletions packages/launcher/lib/linux/index.ts
Expand Up @@ -3,8 +3,33 @@ import type { FoundBrowser, Browser, PathData } from '../types'
import { notInstalledErr } from '../errors'
import { utils } from '../utils'
import os from 'os'
import { promises as fs } from 'fs'
import path from 'path'
import Bluebird from 'bluebird'
import which from 'which'

async function isFirefoxSnap (binary: string): Promise<boolean> {
try {
return await Bluebird.resolve((async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to avoid Bluebird promises in newly written code when possible. Especially since we're already inside an async function, do we need Bluebird.resolve here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using Bluebird.resolve to get access to .timeout. I could Promise.race/new Promise/setTimeout but I figure that unless we are planning on moving all the code in the code base to not use Bluebird, might as well use the utilities we have. I also generally agree that BB should be avoided in new code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is something I would like to do at some point, remove bluebird from our existing code. But adding timeout utility functions is beyond the scope of this PR, fair enough.

const binaryPath = await which(binary)

// read the first 16kb, don't read the entire file into memory in case it is a binary
const fd = await fs.open(binaryPath, 'r')
// @ts-ignore - needs @types/node at least 16
// https://github.com/cypress-io/cypress/issues/21329
const { buffer, bytesRead } = await fd.read<Buffer>({ length: 16384 })

await fd.close()

return buffer.slice(0, bytesRead).toString('utf8').includes('exec /snap/bin/firefox')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Today I learned about reading only the opening bytes of a file! This seemed odd (particularly the slice), so I poked around the docs and command line, and lo, you have done it correctly. :)

})())
.timeout(30000)
} catch (err) {
log('failed to check if Firefox is a snap, assuming it isn\'t %o', { err, binary })

return false
}
}

function getLinuxBrowser (
name: string,
Expand Down Expand Up @@ -43,11 +68,25 @@ function getLinuxBrowser (
throw notInstalledErr(binary)
}

const maybeSetSnapProfilePath = (versionString: string) => {
if (os.platform() === 'linux' && name === 'chromium' && versionString.endsWith('snap')) {
const maybeSetSnapProfilePath = async (versionString: string) => {
if (os.platform() !== 'linux') return

if (name === 'chromium' && versionString.endsWith('snap')) {
// when running as a snap, chromium can only write to certain directories
// @see https://github.com/cypress-io/cypress/issues/7020
log('chromium is running as a snap, changing profile path')
foundBrowser.profilePath = path.join(os.homedir(), 'snap', 'chromium', 'current')

return
}

if (name === 'firefox' && (await isFirefoxSnap(binary))) {
// if the binary in the path points to a script that calls the snap, set a snap-specific profile path
// @see https://github.com/cypress-io/cypress/issues/19793
log('firefox is running as a snap, changing profile path')
foundBrowser.profilePath = path.join(os.homedir(), 'snap', 'firefox', 'current')

return
}
}

Expand Down
4 changes: 3 additions & 1 deletion packages/launcher/package.json
Expand Up @@ -18,13 +18,15 @@
"fs-extra": "9.1.0",
"lodash": "^4.17.21",
"plist": "3.0.5",
"semver": "7.3.5"
"semver": "7.3.5",
"which": "2.0.2"
},
"devDependencies": {
"@packages/ts": "0.0.0-development",
"chai": "3.5.0",
"chai-as-promised": "7.1.1",
"mocha": "3.5.3",
"mock-fs": "5.1.2",
"shelljs": "0.8.5",
"sinon": "^10.0.0",
"sinon-chai": "3.4.0",
Expand Down
37 changes: 36 additions & 1 deletion packages/launcher/test/unit/linux_spec.ts
Expand Up @@ -10,12 +10,13 @@ import { expect } from 'chai'
import { utils } from '../../lib/utils'
import os from 'os'
import sinon, { SinonStub } from 'sinon'
import mockFs from 'mock-fs'

describe('linux browser detection', () => {
let execa: SinonStub
let cachedEnv = { ...process.env }

beforeEach(() => {
sinon.restore()
execa = sinon.stub(utils, 'getOutput')

sinon.stub(os, 'platform').returns('linux')
Expand All @@ -34,6 +35,12 @@ describe('linux browser detection', () => {
.resolves({ stdout: 'foo-browser v9001.1.2.3' })
})

afterEach(() => {
Object.assign(process.env, cachedEnv)
mockFs.restore()
sinon.restore()
})

it('detects browser by running --version', () => {
const goal = goalBrowsers[0]
const checkBrowser = (browser) => {
Expand Down Expand Up @@ -72,6 +79,34 @@ describe('linux browser detection', () => {
return detect().then(checkBrowser)
})

// https://github.com/cypress-io/cypress/issues/19793
it('sets profilePath on snapcraft firefox', async () => {
process.env.PATH = '/bin'
mockFs({
'/bin/firefox': mockFs.symlink({ path: '/usr/bin/firefox' }),
'/usr/bin/firefox': mockFs.file({ mode: 0o777, content: 'foo bar foo bar foo bar\nexec /snap/bin/firefox\n' }),
})

execa.withArgs('firefox', ['--version'])
.resolves({ stdout: 'Mozilla Firefox 99.2.3' })

sinon.stub(os, 'homedir').returns('/home/foo')

const [browser] = await detect()

expect(browser).to.deep.equal({
channel: 'stable',
name: 'firefox',
family: 'firefox',
displayName: 'Firefox',
majorVersion: 99,
minSupportedVersion: 86,
path: 'firefox',
profilePath: '/home/foo/snap/firefox/current',
version: '99.2.3',
})
})

// https://github.com/cypress-io/cypress/issues/6669
it('detects browser if the --version stdout is multiline', () => {
execa.withArgs('multiline-foo', ['--version'])
Expand Down
53 changes: 33 additions & 20 deletions yarn.lock
Expand Up @@ -4140,7 +4140,7 @@

"@jest/types@^26.3.0", "@jest/types@^26.6.2":
version "26.6.2"
resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e"
resolved "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e"
integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==
dependencies:
"@types/istanbul-lib-coverage" "^2.0.0"
Expand Down Expand Up @@ -8268,7 +8268,7 @@

"@types/cheerio@*", "@types/cheerio@0.22.21":
version "0.22.21"
resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.21.tgz#5e37887de309ba11b2e19a6e14cad7874b31a8a3"
resolved "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.21.tgz#5e37887de309ba11b2e19a6e14cad7874b31a8a3"
integrity sha512-aGI3DfswwqgKPiEOTaiHV2ZPC9KEhprpgEbJnv0fZl3SGX0cGgEva1126dGrMC6AJM6v/aihlUgJn9M5DbDZ/Q==
dependencies:
"@types/node" "*"
Expand Down Expand Up @@ -8370,7 +8370,7 @@

"@types/enzyme@*", "@types/enzyme@3.10.5":
version "3.10.5"
resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.10.5.tgz#fe7eeba3550369eed20e7fb565bfb74eec44f1f0"
resolved "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.5.tgz#fe7eeba3550369eed20e7fb565bfb74eec44f1f0"
integrity sha512-R+phe509UuUYy9Tk0YlSbipRpfVtIzb/9BHn5pTEtjJTF5LXvUjrIQcZvNyANNEyFrd2YGs196PniNT1fgvOQA==
dependencies:
"@types/cheerio" "*"
Expand Down Expand Up @@ -10818,9 +10818,9 @@ ansi-regex@^3.0.0:
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=

ansi-regex@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
version "4.1.1"
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed"
integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==

ansi-regex@^5.0.0, ansi-regex@^5.0.1:
version "5.0.1"
Expand Down Expand Up @@ -14123,7 +14123,7 @@ collection-visit@^1.0.0:
map-visit "^1.0.0"
object-visit "^1.0.0"

color-convert@^1.9.0, color-convert@^1.9.1:
color-convert@^1.9.0, color-convert@^1.9.3:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
Expand All @@ -14147,26 +14147,34 @@ color-name@^1.0.0, color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==

color-string@1.5.5, color-string@^1.5.4:
color-string@1.5.5:
version "1.5.5"
resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014"
integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==
dependencies:
color-name "^1.0.0"
simple-swizzle "^0.2.2"

color-string@^1.6.0:
version "1.9.1"
resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4"
integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==
dependencies:
color-name "^1.0.0"
simple-swizzle "^0.2.2"

color-support@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==

color@^3.0.0, color@^3.1.1:
version "3.1.3"
resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e"
integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==
version "3.2.1"
resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164"
integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==
dependencies:
color-convert "^1.9.1"
color-string "^1.5.4"
color-convert "^1.9.3"
color-string "^1.6.0"

colorette@^1.1.0, colorette@^1.2.1, colorette@^1.2.2:
version "1.2.2"
Expand Down Expand Up @@ -27246,6 +27254,11 @@ mock-fs@5.1.1:
resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-5.1.1.tgz#d4c95e916abf400664197079d7e399d133bb6048"
integrity sha512-p/8oZ3qvfKGPw+4wdVCyjDxa6wn2tP0TCf3WXC1UyUBAevezPn1TtOoxtMYVbZu/S/iExg+Ghed1busItj2CEw==

mock-fs@5.1.2:
version "5.1.2"
resolved "https://registry.npmjs.org/mock-fs/-/mock-fs-5.1.2.tgz#6fa486e06d00f8793a8d2228de980eff93ce6db7"
integrity sha512-YkjQkdLulFrz0vD4BfNQdQRVmgycXTV7ykuHMlyv+C8WCHazpkiQRDthwa02kSyo8wKnY9wRptHfQLgmf0eR+A==

mock-require@3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/mock-require/-/mock-require-3.0.3.tgz#ccd544d9eae81dd576b3f219f69ec867318a1946"
Expand Down Expand Up @@ -27755,9 +27768,9 @@ node-addon-api@^1.6.3:
integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==

node-addon-api@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239"
integrity sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw==
version "3.2.1"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==

node-dir@^0.1.10:
version "0.1.17"
Expand Down Expand Up @@ -35316,7 +35329,7 @@ socket.io-client@4.0.1:

socket.io-parser@4.0.4, socket.io-parser@~3.3.0, socket.io-parser@~3.4.0, socket.io-parser@~4.0.3, socket.io-parser@~4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.0.4.tgz#9ea21b0d61508d18196ef04a2c6b9ab630f4c2b0"
resolved "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz#9ea21b0d61508d18196ef04a2c6b9ab630f4c2b0"
integrity sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==
dependencies:
"@types/component-emitter" "^1.2.10"
Expand Down Expand Up @@ -37950,7 +37963,7 @@ typescript@^4.2.3, typescript@^4.4.4:

ua-parser-js@0.7.24, ua-parser-js@^0.7.18:
version "0.7.24"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.24.tgz#8d3ecea46ed4f1f1d63ec25f17d8568105dc027c"
resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.24.tgz#8d3ecea46ed4f1f1d63ec25f17d8568105dc027c"
integrity sha512-yo+miGzQx5gakzVK3QFfN0/L9uVhosXBBO7qmnk7c2iw1IhL212wfA3zbnI54B0obGwC/5NWub/iT9sReMx+Fw==

uc.micro@^1.0.1, uc.micro@^1.0.5:
Expand Down Expand Up @@ -39398,7 +39411,7 @@ vue-style-loader@^4.1.0, vue-style-loader@^4.1.2:

vue-template-compiler@2.6.12, vue-template-compiler@^2.6.11:
version "2.6.12"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz#947ed7196744c8a5285ebe1233fe960437fcc57e"
resolved "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz#947ed7196744c8a5285ebe1233fe960437fcc57e"
integrity sha512-OzzZ52zS41YUbkCBfdXShQTe69j1gQDZ9HIX8miuC9C3rBCk9wIRjLiZZLrmX9V+Ftq/YEyv1JaVr5Y/hNtByg==
dependencies:
de-indent "^1.0.2"
Expand Down Expand Up @@ -40324,7 +40337,7 @@ which@1.3.1, which@^1.1.1, which@^1.2.12, which@^1.2.14, which@^1.2.4, which@^1.

which@2.0.2, which@^2.0.1, which@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
dependencies:
isexe "^2.0.0"
Expand Down