Skip to content

Commit aa42c41

Browse files
chan1cyrus2dignifiedquire
authored andcommitted
feat: add an option to run the tests by dynamically loading test scripts without iframe
1 parent eb407ab commit aa42c41

10 files changed

Lines changed: 297 additions & 21 deletions

File tree

client/karma.js

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,31 @@ var Karma = function (socket, iframe, opener, navigator, location) {
4949
var childWindow = null
5050
var navigateContextTo = function (url) {
5151
if (self.config.useIframe === false) {
52-
// If there is a window already open, then close it
53-
// DEV: In some environments (e.g. Electron), we don't have setter access for location
54-
if (childWindow !== null && childWindow.closed !== true) {
55-
childWindow.close()
52+
// run in new window
53+
if (self.config.runInParent === false) {
54+
// If there is a window already open, then close it
55+
// DEV: In some environments (e.g. Electron), we don't have setter access for location
56+
if (childWindow !== null && childWindow.closed !== true) {
57+
childWindow.close()
58+
}
59+
childWindow = opener(url)
60+
// run context on parent element and dynamically loading scripts
61+
} else if (url !== 'about:blank') {
62+
var loadScript = function (idx) {
63+
if (idx < window.__karma__.scriptUrls.length) {
64+
var ele = document.createElement('script')
65+
ele.src = window.__karma__.scriptUrls[idx]
66+
ele.onload = function () {
67+
loadScript(idx + 1)
68+
}
69+
document.body.appendChild(ele)
70+
} else {
71+
window.__karma__.loaded()
72+
}
73+
}
74+
loadScript(0)
5675
}
57-
childWindow = opener(url)
76+
// run in iframe
5877
} else {
5978
iframe.src = url
6079
}

client/updater.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ var VERSION = require('./constants').VERSION
22

33
var StatusUpdater = function (socket, titleElement, bannerElement, browsersElement) {
44
var updateBrowsersInfo = function (browsers) {
5+
if (!browsersElement) {
6+
return
7+
}
58
var items = []
69
var status
710
for (var i = 0; i < browsers.length; i++) {
@@ -13,6 +16,9 @@ var StatusUpdater = function (socket, titleElement, bannerElement, browsersEleme
1316

1417
var updateBanner = function (status) {
1518
return function (param) {
19+
if (!titleElement || !bannerElement) {
20+
return
21+
}
1622
var paramStatus = param ? status.replace('$', param) : status
1723
titleElement.innerHTML = 'Karma v' + VERSION + ' - ' + paramStatus
1824
bannerElement.className = status === 'connected' ? 'online' : 'offline'

docs/config/01-configuration-file.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,14 @@ How this value is used is up to your test adapter - you should check your adapte
225225
If true, Karma runs the tests inside an iFrame. If false, Karma runs the tests in a new window. Some tests may not run in an
226226
iFrame and may need a new window to run.
227227

228+
## client.runInParent
229+
**Type:** Boolean
230+
231+
**Default:** `false`
232+
233+
**Description:** Run the tests on the same window as the client, without using iframe or a new window
234+
235+
If true, Karma runs the tests inside the original window without using iframe. It will load the test scripts dynamically.
228236

229237
## client.captureConsole
230238
**Type:** Boolean
@@ -289,6 +297,14 @@ Disable this when you need to load external scripts that are served without the
289297
**Description:** If `null` (default), uses karma's own `debug.html` file.
290298

291299

300+
## customClientContextFile
301+
**Type:** string
302+
303+
**Default:** `null`
304+
305+
**Description:** If `null` (default), uses karma's own `client_with_context.html` file (which is used when client.runInParent set to true).
306+
307+
292308
## customHeaders
293309
**Type:** Array
294310

lib/config.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,15 @@ var normalizeConfig = function (config, configFilePath) {
138138
config.exclude = config.exclude.map(basePathResolve)
139139
config.customContextFile = config.customContextFile && basePathResolve(config.customContextFile)
140140
config.customDebugFile = config.customDebugFile && basePathResolve(config.customDebugFile)
141+
config.customClientContextFile = config.customClientContextFile && basePathResolve(config.customClientContextFile)
141142

142143
// normalize paths on windows
143144
config.basePath = helper.normalizeWinPath(config.basePath)
144145
config.files = config.files.map(createPatternMapper(helper.normalizeWinPath))
145146
config.exclude = config.exclude.map(helper.normalizeWinPath)
146147
config.customContextFile = helper.normalizeWinPath(config.customContextFile)
147148
config.customDebugFile = helper.normalizeWinPath(config.customDebugFile)
149+
config.customClientContextFile = helper.normalizeWinPath(config.customClientContextFile)
148150

149151
// normalize urlRoot
150152
config.urlRoot = normalizeUrlRoot(config.urlRoot)
@@ -183,6 +185,16 @@ var normalizeConfig = function (config, configFilePath) {
183185
config.autoWatch = false
184186
}
185187

188+
if (config.runInParent) {
189+
log.debug('useIframe set to false, because using runInParent')
190+
config.useIframe = false
191+
}
192+
193+
if (!config.singleRun && !config.useIframe && config.runInParent) {
194+
log.debug('singleRun set to true, because using runInParent')
195+
config.singleRun = true
196+
}
197+
186198
if (helper.isString(config.reporters)) {
187199
config.reporters = config.reporters.split(',')
188200
}
@@ -296,6 +308,7 @@ var Config = function () {
296308
}
297309
this.customContextFile = null
298310
this.customDebugFile = null
311+
this.customClientContextFile = null
299312
this.exclude = []
300313
this.logLevel = constant.LOG_INFO
301314
this.colors = true
@@ -320,6 +333,7 @@ var Config = function () {
320333
this.defaultClient = this.client = {
321334
args: [],
322335
useIframe: true,
336+
runInParent: false,
323337
captureConsole: true,
324338
clearContext: true
325339
}

lib/middleware/karma.js

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ var createKarmaMiddleware = function (
8989
var client = injector.get('config.client')
9090
var customContextFile = injector.get('config.customContextFile')
9191
var customDebugFile = injector.get('config.customDebugFile')
92+
var customClientContextFile = injector.get('config.customClientContextFile')
9293
var jsVersion = injector.get('config.jsVersion')
9394
var includeCrossOriginAttribute = injector.get('config.crossOriginAttribute')
9495

@@ -112,11 +113,16 @@ var createKarmaMiddleware = function (
112113

113114
// serve client.html
114115
if (requestUrl === '/') {
115-
return serveStaticFile('/client.html', requestedRangeHeader, response, function (data) {
116-
return data
117-
.replace('\n%X_UA_COMPATIBLE%', getXUACompatibleMetaElement(request.url))
118-
.replace('%X_UA_COMPATIBLE_URL%', getXUACompatibleUrl(request.url))
119-
})
116+
// redirect client_with_context.html
117+
if (!client.useIframe && client.runInParent) {
118+
requestUrl = '/client_with_context.html'
119+
} else { // serve client.html
120+
return serveStaticFile('/client.html', requestedRangeHeader, response, function (data) {
121+
return data
122+
.replace('\n%X_UA_COMPATIBLE%', getXUACompatibleMetaElement(request.url))
123+
.replace('%X_UA_COMPATIBLE_URL%', getXUACompatibleUrl(request.url))
124+
})
125+
}
120126
}
121127

122128
// serve karma.js, context.js, and debug.js
@@ -139,11 +145,12 @@ var createKarmaMiddleware = function (
139145
// or debug.html - execution context without channel to the server
140146
var isRequestingContextFile = requestUrl === '/context.html'
141147
var isRequestingDebugFile = requestUrl === '/debug.html'
142-
if (isRequestingContextFile || isRequestingDebugFile) {
148+
var isRequestingClientContextFile = requestUrl === '/client_with_context.html'
149+
if (isRequestingContextFile || isRequestingDebugFile || isRequestingClientContextFile) {
143150
return filesPromise.then(function (files) {
144151
var fileServer
145152
var requestedFileUrl
146-
log.debug('custom files', customContextFile, customDebugFile)
153+
log.debug('custom files', customContextFile, customDebugFile, customClientContextFile)
147154
if (isRequestingContextFile && customContextFile) {
148155
log.debug('Serving customContextFile %s', customContextFile)
149156
fileServer = serveFile
@@ -152,6 +159,10 @@ var createKarmaMiddleware = function (
152159
log.debug('Serving customDebugFile %s', customDebugFile)
153160
fileServer = serveFile
154161
requestedFileUrl = customDebugFile
162+
} else if (isRequestingClientContextFile && customClientContextFile) {
163+
log.debug('Serving customClientContextFile %s', customClientContextFile)
164+
fileServer = serveFile
165+
requestedFileUrl = customClientContextFile
155166
} else {
156167
log.debug('Serving static request %s', requestUrl)
157168
fileServer = serveStaticFile
@@ -161,7 +172,10 @@ var createKarmaMiddleware = function (
161172
fileServer(requestedFileUrl, requestedRangeHeader, response, function (data) {
162173
common.setNoCacheHeaders(response)
163174

164-
var scriptTags = files.included.map(function (file) {
175+
var scriptTags = []
176+
var scriptUrls = []
177+
for (var i in files.included) {
178+
var file = files.included[i]
165179
var filePath = file.path
166180
var fileExt = path.extname(filePath)
167181

@@ -173,12 +187,16 @@ var createKarmaMiddleware = function (
173187
}
174188
}
175189

190+
scriptUrls.push(filePath)
191+
176192
if (fileExt === '.css') {
177-
return util.format(LINK_TAG_CSS, filePath)
193+
scriptTags.push(util.format(LINK_TAG_CSS, filePath))
194+
continue
178195
}
179196

180197
if (fileExt === '.html') {
181-
return util.format(LINK_TAG_HTML, filePath)
198+
scriptTags.push(util.format(LINK_TAG_HTML, filePath))
199+
continue
182200
}
183201

184202
// The script tag to be placed
@@ -190,8 +208,8 @@ var createKarmaMiddleware = function (
190208
}
191209

192210
var crossOriginAttribute = includeCrossOriginAttribute ? CROSSORIGIN_ATTRIBUTE : ''
193-
return util.format(SCRIPT_TAG, scriptType, filePath, crossOriginAttribute)
194-
})
211+
scriptTags.push(util.format(SCRIPT_TAG, scriptType, filePath, crossOriginAttribute))
212+
}
195213

196214
// TODO(vojta): don't compute if it's not in the template
197215
var mappings = files.served.map(function (file) {
@@ -206,11 +224,14 @@ var createKarmaMiddleware = function (
206224

207225
var clientConfig = 'window.__karma__.config = ' + JSON.stringify(client) + ';\n'
208226

227+
var scriptUrlsJS = 'window.__karma__.scriptUrls = ' + JSON.stringify(scriptUrls) + ';\n'
228+
209229
mappings = 'window.__karma__.files = {\n' + mappings.join(',\n') + '\n};\n'
210230

211231
return data
212232
.replace('%SCRIPTS%', scriptTags.join('\n'))
213233
.replace('%CLIENT_CONFIG%', clientConfig)
234+
.replace('%SCRIPT_URL_ARRAY%', scriptUrlsJS)
214235
.replace('%MAPPINGS%', mappings)
215236
.replace('\n%X_UA_COMPATIBLE%', getXUACompatibleMetaElement(request.url))
216237
})

static/client_with_context.html

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
2+
<!DOCTYPE html>
3+
<!--
4+
This is the combined client and execution context.
5+
Is used for single-shot tests.
6+
-->
7+
<html>
8+
<head>
9+
10+
<title>Karma</title>
11+
<link href="favicon.ico" rel="icon" type="image/x-icon">
12+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
13+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
14+
<style type="text/css">
15+
iframe {
16+
height: 100%;
17+
width: 100%;
18+
border: 0;
19+
}
20+
21+
html, body {
22+
height: 100%;
23+
padding: 0;
24+
margin: 0;
25+
26+
font-family: sans-serif;
27+
}
28+
29+
.offline {
30+
background: #DDD;
31+
}
32+
33+
.online {
34+
background: #6C4;
35+
}
36+
37+
.idle {
38+
}
39+
40+
.executing {
41+
background: #F99;
42+
}
43+
44+
#banner {
45+
padding: 5px 10px;
46+
}
47+
48+
h1 {
49+
font-size: 1.8em;
50+
margin: 0;
51+
padding: 0;
52+
}
53+
54+
ul {
55+
margin: 0;
56+
padding: 0;
57+
58+
list-style: none;
59+
}
60+
61+
li {
62+
padding: 5px 12px;
63+
}
64+
65+
.btn-debug {
66+
float: right;
67+
}
68+
69+
.offline .btn-debug {
70+
display: none;
71+
}
72+
73+
.btn-debug {
74+
-moz-box-shadow:inset 0px 1px 0px 0px #ffffff;
75+
-webkit-box-shadow:inset 0px 1px 0px 0px #ffffff;
76+
box-shadow:inset 0px 1px 0px 0px #ffffff;
77+
background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #ffffff), color-stop(1, #f6f6f6) );
78+
background:-moz-linear-gradient( center top, #ffffff 5%, #f6f6f6 100% );
79+
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f6f6f6');
80+
background-color:#ffffff;
81+
-moz-border-radius:6px;
82+
-webkit-border-radius:6px;
83+
border-radius:6px;
84+
border:1px solid #dcdcdc;
85+
display:inline-block;
86+
color:#666666;
87+
font-family:arial;
88+
font-size:15px;
89+
font-weight:bold;
90+
padding:6px 24px;
91+
text-decoration:none;
92+
text-shadow:1px 1px 0px #ffffff;
93+
}
94+
95+
.btn-debug:hover {
96+
background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #f6f6f6), color-stop(1, #ffffff) );
97+
background:-moz-linear-gradient( center top, #f6f6f6 5%, #ffffff 100% );
98+
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f6f6f6', endColorstr='#ffffff');
99+
background-color:#f6f6f6;
100+
}
101+
</style>
102+
</head>
103+
<body>
104+
<div id="banner" class="offline">
105+
<h1 id="title">Karma - starting</h1>
106+
</div>
107+
<script src="socket.io/socket.io.js"></script>
108+
<script src="karma.js"></script>
109+
<script src="context.js"></script>
110+
<!-- The scripts need to be at the end of body, so that some test running frameworks
111+
(Angular Scenario, for example) need the body to be loaded so that it can insert its magic
112+
into it. If it is before body, then it fails to find the body and crashes and burns in an epic
113+
manner. -->
114+
<script type="text/javascript">
115+
// sets window.__karma__ and overrides console and error handling
116+
%CLIENT_CONFIG%
117+
window.__karma__.setupContext(window);
118+
119+
// All served files with the latest timestamps
120+
%MAPPINGS%
121+
%SCRIPT_URL_ARRAY%
122+
</script>
123+
<!-- Dynamically replaced with <script> tags -->
124+
</body>
125+
</html>

test/client/karma.spec.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ describe('Karma', function () {
4646

4747
it('should open a new window when useIFrame is false', function () {
4848
var config = ck.config = {
49-
useIframe: false
49+
useIframe: false,
50+
runInParent: false
5051
}
5152

5253
socket.emit('execute', config)

0 commit comments

Comments
 (0)